-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Elegant way to filter fields? #750
Comments
So it'll probably be a bit repetitive, but you should be able to achieve this with a custom implementation of
|
@jcorbin Thank you for the quick reply! :) That was basically my approach so far (tested with just I don't really understand:
I don't think this helps me achieve marshaling a JSON object: caddyserver/caddy#1109 (comment) Another seeming problem is that, for example |
Have another look at the core json encoder:
|
Thanks, I'm looking now. What do you mean by "key tracking"? I don't see anything in |
Right, for the json encoder, the only state is "the built buffer". But for anything that wants to do structural things (which filtering or re-writing of fields is fundamentally one such), you may need to track the full key path. The structure will only exist within your custom encoder, let me try to be more specific here with some pseudo code that I'm writing live here in this github comment (buyer beware): type encoder struct {
path []string // stack of keys leading to the current value being encoded
// TODO whatever other encoding state you need
}
func (enc encoder) AddObject(key string, obj ObjectMarshaller) error {
// TODO opportunity to apply key filter before descending
enc.path = append(enc.path, key) // push key
// TODO encode the key somehow
err := enc.AppendObject(obj) // recurses with collect path recursively
enc.path = enc.path[:len(enc.path)-1] // pop key
return err
}
// for all T in ObjectEncoder's required method set:
func (enc encoder) AddT(key string, value T) {
// NOTE at this point, enc.path has all parent keys containing the about to be added key, in case context matters for deciding if you should allow or not
} Sorry for the abortive first comment, took me a while to realize that there are challenges to "Simply Composing" an encoder-wrapper type; read all of this with the hazard that "for our purposes, we consider zap 'done', and I haven't actively touched the code in quite some time" ( I'm reactivating all sorts of dormant mental pathways just to try to answer this for you; buyer beware ;-) ) |
Thank you for taking the effort to demonstrate what you mean! I appreciate it a lot. I am aware that "zap is done" so I'm just trying to get familiar with its architecture. I suppose I wasn't clear on this before, but my wrapper encoder has an underlying Thus, I don't implement this method:
And I can't call that for an underlying encoder because So I'm limited to |
I'm gonna try a different approach, where I wrap the |
Nevermind, that still makes |
The difficulties in writing "filtering middlewares" has come up sporadically in other (now closed) issues before, e.g. #453 fwiw, there are probably others... I believe the status is "possible, but more effort that you'd think, because performance". Maybe sometime after generics, we can consider doing a 2.0 that would better support this use case ( once the state of "because performance" shifts for a library like this... ) |
I see... thanks for pointing me to those issues. (For some reason I hadn't found them.) Although, I have had a slight breakthrough by iterating on my initial attempt. By combining my own Edit: Okay, yeah, it works -- the trick is to sneak my encoder in between the call to AddObject (on the underlying encoder) and MarshalLogObject (which the underlying encoder invokes when its AddObject is called). Since MarshalLogObject takes an encoder, I just make sure it receives my filtering encoder. I believe there are no extra allocations (but it does slightly deepen the call stack). Will post code today or tomorrow. |
Thank you again for your help! I figured it out, I believe. Here is the implementation: https://github.com/caddyserver/caddy/pull/2831/files#diff-600bb63c5e5c442aa4ed554d687f2120 |
Actually, that solution seems to be appending each log entry with the previous one. An initial log, for example, would be:
But then repeating the same request shows:
And then it just keeps multiplying. Do you think that would be something to do with zap's pooling behavior? (I'm in the process of debugging and just wanted to check if this is something you've seen before.) (I am using the console encoder; these snippets are from the end of each line -- but it also happens with the JSON encoder.) |
Ah... so the zap default encoders pool themselves ( Line 42 in 0065243
I don't fully understand what is happening, but: because the encoders pool themselves, encoders get reused? Changing my wrapper's EncodeEntry method from this: for _, field := range fields {
field.AddTo(fe)
}
return fe.wrapped.EncodeEntry(ent, nil) to this: fe.wrapped = fe.wrapped.Clone()
for _, field := range fields {
field.AddTo(fe)
}
return fe.wrapped.EncodeEntry(ent, nil) eliminates the multiplying of fields and it works again. (Notably, |
@mholt did you end up finding an elegant way to retrieve fields from the underlying zap core? I'd imagine this is a common thing to want to do, but haven't found good example.. any other insight you may have since opening the issue? |
@mfridman Yeah, I got this to work at an acceptable level of elegance. Current implementation is here: https://sourcegraph.com/github.com/caddyserver/caddy@v2/-/blob/modules/logging/filterencoder.go Note the calls to It's elegant enough since it only has to be done once. Caddy 2's logging features are actually really neat, since all the heavy lifting like this is done -- you can do things with logs that you can't do in other web servers, thanks to zap. |
Sorry for the delay, just wanted to make sure this was explained: The
A simpler interface would have been to drop the Thanks for sharing the filter encoder, and glad zap is working well for Caddy 2! |
Hi, I've been working with zap and it's great, but I have a question that I haven't been able to solve in a few days' time.
How can I filter out certain fields? Preferably by name (and possibly type). For example, I want to mask IP addresses in fields called "remote_addr" or strip fields called "password".
I've tried making an
Encoder
and wrappedEncodeEntry()
and this works, but not for complex types like objects and arrays. In other words, if a log adds a field withzap.Object("http_request", req)
,EncodeEntry()
has no way to peer inside the object and find offending fields and filter them ... does it?I've also tried implementing
AddString()
andAddObject()
on my Encoder type but then the object loses its structure, and all its fields are flatted into the top level of the context.Any ideas how this can be done?
It is important for regulatory/compliance reasons, apparently.
The text was updated successfully, but these errors were encountered: