-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Integrate Firestore with Firebase platform logging #6507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Generated by 🚫 Danger |
Firestore/core/src/util/config.h
Outdated
@@ -1,57 +0,0 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file shouldn't be deleted (unless there's a replacement?).
Note that config.h was previously generated, but this was changed for compatibility with Swift Package Manager which does not allow for build-time configuration scripts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, originally I deleted the wrong config.h
file. Thanks for explaining!
@@ -0,0 +1,33 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the old location of the file (before we shortened all the paths). This should be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.
|
||
class FirebasePlatformLoggingApple : public FirebasePlatformLogging { | ||
public: | ||
explicit FirebasePlatformLoggingApple(FIRApp* app); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Objective-C types should have their nullability defined, either by wrapping in NS_ASSUME_NONNNULL_BEGIN
/END
or by annotating the pointers directly (FIRApp* _Nonnull app
) .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
#import "FirebaseCore/Sources/Private/FIRAppInternal.h" | ||
#import "FirebaseCore/Sources/Private/FIRHeartbeatInfo.h" | ||
#import "FirebaseCore/Sources/Private/FIROptionsInternal.h" | ||
//#import "FirebaseCore/Sources/Public/FirebaseCore/FIRApp.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove commented-out code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, sorry.
* Wraps the platform-dependent functionality associated with Firebase platform | ||
* logging. | ||
*/ | ||
class FirebasePlatformLogging { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Android implementation, we have a notion of a GrpcMetadataProvider
which does everything required to set the headers. Doing something similar here could make this a much narrower interface.
On the other hand, an interface like this one makes this information independent of the gRPC implementation which has some merit. Do you have any thoughts on how to make these platforms work similarly? This doesn't seem like something that has to be different on the different platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I started working on this, I indeed wanted to avoid a dependency on gRPC. However, this resulted in an interface that requires a lot of handholding from the client code (it must make sure to call all the IsFooAvailable
functions, for example), so it seems that a dependency is a small price to pay for a more self-sufficient class.
Mimicking Android would be a bigger change -- the Android interface is pretty abstract, and it seems like it should provide all the extra metadata (including Cloud headers). If you feel strongly about this, I can do this in a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Android implementation is also imperfect. My original thought there was that separate concerns could supply separate metadata providers. That is, Firebase headers from one provider, Cloud headers from another, Auth perhaps from yet another. From that point of view, I think what you have here fits that goal quite well. Whether or not you see any benefit is another matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My original thought there was that separate concerns could supply separate metadata providers.
That's pretty much how I see it as well.
@@ -248,6 +249,7 @@ class GrpcStreamTester { | |||
|
|||
grpc::CompletionQueue grpc_queue_; | |||
FakeGrpcQueue fake_grpc_queue_; | |||
std::unique_ptr<FirebasePlatformLogging> firebase_platform_logging_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FirebasePlatformLogging
could be forward declared in this header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -25,6 +25,7 @@ | |||
#include "Firestore/core/src/auth/token.h" | |||
#include "Firestore/core/src/core/database_info.h" | |||
#include "Firestore/core/src/remote/connectivity_monitor.h" | |||
#include "Firestore/core/src/remote/firebase_platform_logging.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a forward declaration? Here and in all other headers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (in all headers, I think).
* Wraps the platform-dependent functionality associated with Firebase platform | ||
* logging. | ||
*/ | ||
class FirebasePlatformLogging { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider renaming this to a regular noun rather than a gerund (i.e. "logger" vs "logging").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intention was to go with the exact name used for the feature; getting this approach to its logical conclusion would result in the awkward FirebasePlatformLoggingLogger
. I'm not against using FirebasePlatformLogger
if you think not following the feature name 100% isn't an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a forest, the person who cuts the trees is a "logger", while the larger practice is "logging". They're already grammatically related without resorting to calling the lumberjack a "tree logging logger".
My general skepticism with the "firebase platform logging" name comes from the fact that the term names the larger effort including something client-side that sends data, server-side collection, logs that are written, and the warehousing scripts to bucket the data. Since this is just a part of that larger effort, it seems misleading to name this smaller part with the name of whole thing.
To be fully descriptive you might name this thing "firebase platform logging metadata provider", since this doesn't really log data so much as attach headers to a request (aka provide request metadata). I think that's probably more of a mouthful than is warranted, given this thing's narrow scope.
If you agree with my idea above about (eventually) partitioning the header-providing bits along the lines of Firebase/Cloud/Auth/etc, possibly it's just sufficient to call this a FirebaseMetadataProvider.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I treat the feature name as an opaque identifier, so I would see it as similar to "LCD display/PIN number/etc.". However, I like the name FirebaseMetadataProvider
, so I might as well just go with that.
I fully agree with partitioning metadata providers into Cloud/Firebase/etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Structurally LGTM, though with several additional bits of optional feedback.
* Wraps the platform-dependent functionality associated with Firebase platform | ||
* logging. | ||
*/ | ||
class FirebasePlatformLogging { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Android implementation is also imperfect. My original thought there was that separate concerns could supply separate metadata providers. That is, Firebase headers from one provider, Cloud headers from another, Auth perhaps from yet another. From that point of view, I think what you have here fits that goal quite well. Whether or not you see any benefit is another matter.
* Wraps the platform-dependent functionality associated with Firebase platform | ||
* logging. | ||
*/ | ||
class FirebasePlatformLogging { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a forest, the person who cuts the trees is a "logger", while the larger practice is "logging". They're already grammatically related without resorting to calling the lumberjack a "tree logging logger".
My general skepticism with the "firebase platform logging" name comes from the fact that the term names the larger effort including something client-side that sends data, server-side collection, logs that are written, and the warehousing scripts to bucket the data. Since this is just a part of that larger effort, it seems misleading to name this smaller part with the name of whole thing.
To be fully descriptive you might name this thing "firebase platform logging metadata provider", since this doesn't really log data so much as attach headers to a request (aka provide request metadata). I think that's probably more of a mouthful than is warranted, given this thing's narrow scope.
If you agree with my idea above about (eventually) partitioning the header-providing bits along the lines of Firebase/Cloud/Auth/etc, possibly it's just sufficient to call this a FirebaseMetadataProvider.
Thoughts?
} | ||
} | ||
|
||
std::string FirebasePlatformLoggingApple::GetUserAgent() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely optional: with the exception of GetGmpAppId
, none of these access any state in this instance. They could just be free functions in the anonymous namespace. Even GetGmpAppId
could be phrased that way (by taking the FIRApp*
as a parameter).
Alternatively, you could make these two static rather than const members.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, done.
@@ -109,6 +112,8 @@ class GrpcConnection { | |||
|
|||
void RegisterConnectivityMonitor(); | |||
|
|||
void AddFirebasePlatformLoggingHeader(grpc::ClientContext& context) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This declaration does not seem to have a definition (but perhaps I missed it)? It also seems not to have any uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, sorry.
namespace firestore { | ||
namespace remote { | ||
|
||
class FirebasePlatformLoggingNoOp : public FirebasePlatformLogging { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: if the base class were to have a default no-op implementation, this class would be unnecessary.
At that point, anyone that wanted an implementation could also just absl::make_unique<FirebasePlatformLogging>()
for themselves, also removing the need for CreateNoOpFirebasePlatformLogging
.
Note that I have no strong preference about whether or not there's an explicit no-op type. I can see the case for it but this could also be simpler without it.
Secondarily, I find the disagreement in word ordering between FirebasePlatformLoggingNoOp
and CreateNoOpFirebasePlatformLogging
somewhat jarring. I suggest picking just one. Customarily the ordering for refinements of a base type seems to be refinement-base (as in the Create function) so I favor that ordering, but also don't feel strongly. See the differences searching google3 for \w+ThreadPool\b lang:c++
vs \bThreadPool\w+ lang:c++
: the former are refinements of thread pool while the latter are mostly either related types (like ThreadPoolOptions
) or classes where ThreadPool
is the refinement of some other base type (like ThreadPoolTaskSystem
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made the word order consistent.
I guess I have a strong preference towards the base-refinement
ordering because it creates natural grouping, though there is a good argument to be made regarding precedent. I'd certainly prefer not to tackle it in this PR.
I'm slightly in favor of having an explicit no-op class because it's not supposed to be used normally. I can move it under test/
in a follow-up if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@VinayGuthal @vkryachko @maksymmalyhin Vinay, Vlad, Maksym -- can you please take a look to make sure the integration is correct? The most relevant parts are in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
} | ||
|
||
std::string GetHeartbeat() { | ||
return std::to_string([FIRHeartbeatInfo heartbeatCodeForTag:@"fire-fst"]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, currently [FIRHeartbeatInfo heartbeatCodeForTag:]
method performs file operations to read and update data in the heartbeat storage. Ideally this call should not happen on the main thread. Also it may be worth to check if the current implementation provides performance good enough for Firestore use case.
Please let me know if you have any questions or concerns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firestore runs all networking and and I/O operations on a dedicated queue. This can only ever be called from that queue so this won't have a chance to run on the main thread.
std::shared_ptr<FirestoreClient> shared_client( | ||
new FirestoreClient(database_info, std::move(credentials_provider), | ||
std::move(user_executor), std::move(worker_queue))); | ||
std::shared_ptr<FirestoreClient> shared_client(new FirestoreClient( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious: any reason not to use make_shared()
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make_shared
requires a public constructor but we're forcing callers to use FirestoreClient::Create
and making the constructor private.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense, thanks for explaining
context.AddMetadata(kXFirebaseClientHeader, GetUserAgent()); | ||
context.AddMetadata(kXFirebaseClientLogTypeHeader, GetHeartbeat()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the headers are sent unconditionally on android:
https://github.com/firebase/firebase-android-sdk/blob/6e286da45f952142d91482219f0ad1e019c2b06d/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/FirebaseClientGrpcMetadataProvider.java#L55
cc @VinayGuthal to confirm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point, thanks. Before I do the change, one thing I need to clarify is whether the GMP App ID should or shouldn't be sent if heartbeat is 0
. @VinayGuthal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think sending the user agent, heartbeat type and GMP App ID may be omitted when the heartbeat type is FIRHeartbeatInfoCodeNone
if the headers are meant to be used for the Platform logging pipeline only as it won't affect instance count estimation. Though I see a potential value in sending the user agent anyway as it may be used for analysis potential SDK issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm the concern at the time was that we might be causing a latency everytime firestore is used and hence the headers were not sent unconditionally. As @maksymmalyhin mentioned it won't affect instance count estimation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanations. Changed to not send anything if the heartbeat is 0
(how often does it happen, by the way?), PTAL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the heartbeat is 0 all the time except for the first time in the day.
if (!gmp_app_id.empty()) { | ||
context.AddMetadata(kXFirebaseGmpIdHeader, gmp_app_id); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
android does not send the gmp app id either. @VinayGuthal, curious why that is the case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@VinayGuthal Do you know why Android doesn't send the GMP App ID? This is something we're currently planning to add.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm looks like we do collect for firebase-iid. To estimate the number of devices using firebase we wouldn't need the GMP_APP_ID however. However the platform log proto has GMP_APP_ID as a optional string which can be useful for other metrics. I can change the android implementation to include the GMP_APP_ID as well.
@@ -36,8 +36,8 @@ | |||
return MakeString([FIRApp firebaseUserAgent]); | |||
} | |||
|
|||
std::string GetHeartbeat() { | |||
return std::to_string([FIRHeartbeatInfo heartbeatCodeForTag:@"fire-fst"]); | |||
int GetHeartbeat() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder why casting it to int
instead of using the FIRHeartbeatInfoCode enum? I think we are losing readability in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point, done.
@@ -52,8 +52,13 @@ | |||
|
|||
void FirebaseMetadataProviderApple::UpdateMetadata( | |||
grpc::ClientContext& context) { | |||
int heartbeat = GetHeartbeat(); | |||
if (heartbeat == 0) { | |||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional: the method name looks generic enough for other thing unrelated to platform logs to be added there. This early exist may be very easy to miss, so the additional metadata may be unintentionally dropped. I would either wrap platform logs metadata into if(heartbeat != FIRHeartbeatInfoCodeNone)
or introduce a separate method with a name like addPlatformLoggingMetadata()
with the early exist which will be called from UpdateMetadata()
. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is intended specifically for the Firebase platform logging-related functionality. We may abstract away other things (e.g. Cloud headers) in a similar class, but it would be a separate one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for clarification. IMO, the class and method names look a bit too generic in this case, but I don't feel too strong about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
The sample user agent string I'm getting in XCode is `apple-platform/ios apple-sdk/17E8258 fire-fst/1.17.1 fire-ios/6.10.2 swift/true xcode/11E503a`.
The sample user agent string I'm getting in XCode is
apple-platform/ios apple-sdk/17E8258 fire-fst/1.17.1 fire-ios/6.10.2 swift/true xcode/11E503a
.