Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upAn RFC for structured logging #296
Conversation
This comment has been minimized.
This comment has been minimized.
diwic
commented
Oct 14, 2018
|
As for serde, I want to raise an issue of serde not being capable of serializing empty arrays correctly, i e, the type information gets lost. As this issue was closed as "wontfix", this made serde impossible to use for some formats (such as mine). Maybe that limitation is not severe enough for this case - that's up to the rest of you to decide - but I think it's something to be aware of when choosing a serialization crate. |
This comment has been minimized.
This comment has been minimized.
dpc
commented
Oct 14, 2018
|
The only thing that worries me is the inability to express data that can be sent to another thread without serialization. One idea that I have is putting additional bound on every value: https://doc.rust-lang.org/std/borrow/trait.ToOwned.html This way values are always taken as references, but a async-logger implementation can just call |
This comment has been minimized.
This comment has been minimized.
dpc
commented
Oct 14, 2018
•
|
@diwic while I typically love using It seems to me, having serde dependency behind a feature flag, and thus the ability to support other solutions (or serde versions) is a good thing to have. The pain point with libraries like logging is that they are interoperability point of the whole ecosystem, and having to introduce any breaking changes (like public dependency) can be a painful experience for everyone. |
This comment has been minimized.
This comment has been minimized.
|
I'm not convinced that a serde dependency is the right choice for this use case. The log crate needs to focus on being lightweight in terms of code generation in particular. I think a virtual-dispatch based approach may work out better there. |
This comment has been minimized.
This comment has been minimized.
bitemyapp
commented
Oct 14, 2018
|
For some prior art, over in Haskell-land I've been using katip. Not perfect (perhaps particularly on the serialization side?), but it's worked better than previous libraries I'd used that attempted to provide single input / multi output structured logging w/ support for ordinary text logs. A faculty for flipping between nginx-style / apache compatible / etc. text logging would be neat too. |
This comment has been minimized.
This comment has been minimized.
TimNN
commented
Oct 14, 2018
|
I haven't wrapped my head around all the details yet, but requiring serde as a depending feels kind of heavy handed to me (especially since, as far as I can tell, there is no way for the top-level crate to disable the serde integration). I would personally feel better about this if there was another feature, only for use in the top-level crate, that would act as a "kill switch", removing the dependency on serde. (I'm not sure if that is feasible. For the log-recording part it should be, I think; for the sinks I'm not so sure). |
This comment has been minimized.
This comment has been minimized.
|
It sounds like we're not sold on depending on So maybe we can find an API that doesn't bake in |
This comment has been minimized.
This comment has been minimized.
gnzlbg
commented
Oct 14, 2018
|
I think that adding a dependency to include a trait is not a big issue. The problem is that in the case of If instead of requiring full |
This comment has been minimized.
This comment has been minimized.
This might just be my opinion after being immersed in it for so long, but I can't think of any case where non-structured logging is preferable to structured logging. If it's available we should be using it.
|
This comment has been minimized.
This comment has been minimized.
diwic
commented
Oct 14, 2018
|
So, the easiest way is just to say that keys are Because the moment we cross that line and say that we want something else than strings in the log backend, may it be integers, arrays, maps, booleans or something else, the question becomes much more complex because a) we then have to decide what we want to support and what we want to not support and b) all log backends need to implement support for this as well. |
This comment has been minimized.
This comment has been minimized.
christophebiocca
commented
Oct 14, 2018
|
One question that isn't clearly answered from reading the RFC: |
This comment has been minimized.
This comment has been minimized.
matthieu-m
commented
Oct 14, 2018
|
Is it possible/desirable to have a context argument? The examples show key-value pairs being passed explicitly, however I could imagine wanting to prepare a "map" of key-value pairs ahead of time (a context) and passing that to multiple logs. |
This comment has been minimized.
This comment has been minimized.
dpc
commented
Oct 14, 2018
•
Your logging backend could implement it and be initialized with desired metadata, that would add to key-value pairs of each If you want explicit context, that you could manipulate and pass at runtime, like in |
This comment has been minimized.
This comment has been minimized.
I like the sentiment here, but I don't think we can really start with
Sure can! While preparing this RFC I put together an an experimental repo for this kind of contextual logging.
This is something we do in .NET using message templates. I think an alternative implementation of the |
This comment has been minimized.
This comment has been minimized.
|
My sticking points on the serialization question are:
We possibly can find a way to achieve that without baking in |
This comment has been minimized.
This comment has been minimized.
@sfackler this is using virtual dispatch by way of The lightweight code generation requirement is an interesting one I haven't really thought about at all. Could you elaborate on it a bit more? Which usergroup is it important to? |
This comment has been minimized.
This comment has been minimized.
softprops
commented
Oct 14, 2018
|
I believe the author of serde may also have some ideas for a lighter weight serde alternative |
This comment has been minimized.
This comment has been minimized.
|
Looking at the complexity concern with |
This comment has been minimized.
This comment has been minimized.
diwic
commented
Oct 15, 2018
•
Strings are indeed all we get with such a proposal, which makes writing a backend to "log" a lot easier, than if it has to support all data types that someone somewhere has decided. If there are technical difficulties with starting with "anything Display" as values, how about starting with only |
This comment has been minimized.
This comment has been minimized.
Dowwie
commented
Oct 15, 2018
|
This seems like a proposal for a white horse with black stripes that we are told ought not be considered a zebra. I hope that I'm wrong but signs are showing that the stdlibbers are hungry to grow stdlib beyond its original mandate. I'm not an advocate of stdlib crates competing with those of the rest of the ecoystem. stdlib Rust should foster cooperation, not competition. So, what would I need slog for if its main features were made available in a stdlib log crate? This is a question I and others will have if core functional overlaps exist. I'm very interested in seeing a proposal for structured logging that is novel and does not overlap. Why would the authors of slog want to refactor working code just to make it line with stdlib team designs? Why should they? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I totally agree with you there. Having to write a fn write_pretty(w: impl Write, r: &Record) -> io::Result<()> {
// Write the first line of the log record
...
// Write each key-value pair using the `WriteKeyValues` visitor
record
.key_values()
.try_for_each(|k, v| writeln!(&mut w, "{}: {:?}", k, v))
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.into_error()))
}For properly structured formats, where a fn write_json(w: impl Write, r: &Record) -> io::Result<()> {
let r = SerializeRecord {
lvl: r.level(),
ts: epoch_millis(),
msg: r.args().to_string(),
props: r.key_values().serialize_as_map(),
};
serde_json::to_writer(w, &r)
}
#[derive(Serialize)]
struct SerializeRecord<KVS> {
lvl: Level,
ts: u64,
msg: String,
#[serde(flatten)]
props: KVS,
}So I think the I have also been hacking around with |
This comment has been minimized.
This comment has been minimized.
dpc
commented
Oct 15, 2018
•
|
Logging is an interoperability problem. Almost every crate does some logging, and if parts of ecosystem use different logging, then in practice it will degrade to lowest common denominator. Also, I don't know about others but I don't want to be maintaining libraries if I could get the same functionality from (semi-)stdlib.
Most people just take a one that was written and doesn't care. I have used Even less so with logging, where people typically just want to dump a bunch of json logs to ElasticSearch. For |
This comment has been minimized.
This comment has been minimized.
Ralith
commented
Oct 15, 2018
slog already does something like this, I think without proc macros; named format arguments are interpreted as structured metadata in addition to being formatted, while anonymous ones aren't. It's nice to use. |
This comment has been minimized.
This comment has been minimized.
Well,
This is true, there's a lot more value to get out of structured logging when you use a log store that supports working with structured data, like Elasticsearch. That's why I don't think we should punt on the actual structured serialization support for this RFC.
Ah gotcha. We could do the same thing in info!("the format string with a {number}"; number = 2); |
This comment has been minimized.
This comment has been minimized.
|
I've spent some time going around in circles on The trade-off there that I see is that we'd then need another trait like |
KodrAus
referenced this pull request
Oct 15, 2018
Closed
What might a slimmer serde look like? #1412
This comment has been minimized.
This comment has been minimized.
|
Ok, I spent some time exploring the serialization API more to remove the required This API doesn't require I think this approach can mitigate some of the concerns about |
This comment has been minimized.
This comment has been minimized.
|
Ok, what do we think of this API, based on the serialization proof-of-concept I posted before? I've stuck with the visit terminology over the serialize terminology because actually serializing a key-value pair is just one thing you might want to do with them. Other things include checking whether a specific pair is present and doing something with its primitive type, like comparing the timestamp on a record to one that was previously set in some ambient context to calculate the elapsed time. I changed // --- Set of key-value pairs --- //
pub trait Source {
fn visit<'kvs>(&'kvs self, visitor: &mut dyn SourceVisitor<'kvs>) -> Result<(), Error>;
// --- Provided adapter methods --- //
// Using these you probably don't to build a `SourceVisitor` yourself
fn erase(&self) -> ErasedSource
where
Self: Sized,
{}
fn try_for_each<F, E>(self, f: F) -> Result<(), Error>
where
Self: Sized,
F: FnMut(Key, Value) -> Result<(), E>,
E: Into<Error>,
{}
fn get<'kvs, Q>(&'kvs self, key: Q) -> Option<Value<'kvs>>
where
Q: Borrow<str>,
{}
#[cfg(feature = "serde")]
fn serialize_as_map(self) -> SerializeAsMap<Self>
where
Self: Sized,
{}
...
}
pub trait SourceVisitor<'kvs> {
fn visit_pair(&self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error>;
}
// --- Individual key or value --- //
// Can't be implemented manually
// It's either a fixed set of `std` types or `T: Serialize`
pub trait Visit: private::Sealed {
fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error>;
}
#[cfg(not(feature = "serde"))]
impl Visit for $std_ty {} // where $std_ty is `u8`, `bool`, `&str`, `&Path` etc
#[cfg(feature = "serde")]
impl<T: Serialize + ?Sized> Visit for T {} // covers all $std_ty impls + a bunch more
pub trait Visitor {
fn visit(&mut self, v: &dyn Visit) -> Result<(), Error> {
v.visit(self)
}
fn visit_i64(&mut self, v: i64) -> Result<(), Error>;
fn visit_u64(&mut self, v: u64) -> Result<(), Error>;
fn visit_f64(&mut self, v: f64) -> Result<(), Error>;
fn visit_bool(&mut self, v: bool) -> Result<(), Error>;
fn visit_char(&mut self, v: char) -> Result<(), Error>;
fn visit_str(&mut self, v: &str) -> Result<(), Error>;
fn visit_bytes(&mut self, v: &[u8]) -> Result<(), Error>;
// If a format is given a complex `serde` datastructure, like a map or sequence
// A format might want to write a placeholder value like `null` instead
fn unsupported(&mut self) -> Result<(), Error> {
Err(Error::msg("unsupported value"))
}
}
// --- Concrete types for keys and values --- //
// `ToOwned::Owned: Send + Sync + 'static`?
// use `Borrow<str>` as the generic trait bound for keys
pub struct Key<'k> {}
impl<'k> Key<'k> {
fn as_str(&self) -> &str {}
}
// `Key` is always `Visit`
// If `serde` is available, it's also `Serialize`
#[cfg(not(feature = "serde"))]
impl<'k> Visit for Key<'k> {}
#[cfg(feature = "serde")]
impl<'k> Serialize for Key<'k> {}
// `ToOwned::Owned: Send + Sync + 'static`?
// use `Visit` as the generic trait bound for values
pub struct Value<'v> {}
// `Value` is always `Visit`
// If `serde` is available, it's also `Serialize`
#[cfg(not(feature = "serde"))]
impl<'v> Visit for Value<'v> {}
#[cfg(feature = "serde")]
impl<'v> Serialize for Value<'v> {}the Why
|
This comment has been minimized.
This comment has been minimized.
bitemyapp
commented
Oct 18, 2018
|
@KodrAus apologies for my ignorance, does |
This comment has been minimized.
This comment has been minimized.
dpc
commented
Oct 18, 2018
|
@bitemyapp This is unrelated to this feature. Technically it is possible with |
This comment has been minimized.
This comment has been minimized.
diwic
commented
Oct 18, 2018
I guess there will be some bikeshedding over exactly what types are to be expected here? E g, I don't see myself wanting to log a Another thought is what to do with |
This comment has been minimized.
This comment has been minimized.
dpc
commented
Oct 18, 2018
|
For comparison: https://docs.rs/slog/2.4.1/slog/trait.Serializer.html |
This comment has been minimized.
This comment has been minimized.
|
@bitemyapp Yeh, this is another feature that's a logical next step from structured logging, but I haven't tried to tackle it as part of this RFC besides trying not to design ourselves into a corner. Personally, I'm not sure that we need to support too much composition in
@diwic Sure, let's colour some bikesheds!
What else do we think? Other APIs for comparison
|
This comment has been minimized.
This comment has been minimized.
|
One major drawback of the design I posted is that while it doesn't technically make So I've been thinking about how So updating the API from before: // --- Set of key-value pairs --- //
pub trait Source {
fn visit<'kvs>(&'kvs self, visitor: &mut dyn SourceVisitor<'kvs>) -> Result<(), Error>;
// --- Provided adapter methods --- //
// Using these you probably don't to build a `SourceVisitor` yourself
fn erase(&self) -> ErasedSource
where
Self: Sized,
{}
fn try_for_each<F, E>(self, f: F) -> Result<(), Error>
where
Self: Sized,
F: FnMut(Key, Value) -> Result<(), E>,
E: Into<Error>,
{}
fn get<'kvs, Q>(&'kvs self, key: Q) -> Option<Value<'kvs>>
where
Q: Borrow<str>,
{}
#[cfg(feature = "serde")]
fn serialize_as_map(self) -> SerializeAsMap<Self>
where
Self: Sized,
{}
...
}
pub trait SourceVisitor<'kvs> {
fn visit_pair(&self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error>;
}
// --- Individual key or value --- //
// Can't be implemented manually
// It's either a fixed set of `std` types or `T: Serialize`
pub trait Visit: private::Sealed {
fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error>;
}
#[cfg(not(feature = "serde"))]
impl Visit for $std_ty {} // where $std_ty is `u8`, `bool`, `&str`, `&Path` etc
#[cfg(feature = "serde")]
impl<T: Serialize + Debug + ?Sized> Visit for T {} // covers all $std_ty impls + a bunch more
pub trait Visitor {
fn visit(&mut self, v: &dyn Visit) -> Result<(), Error> {
v.visit(self)
}
fn visit_i64(&mut self, v: i64) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
fn visit_u64(&mut self, v: u64) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
#[cfg(feature = "i128")]
fn visit_u128(&mut self, v: u128) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
#[cfg(feature = "i128")]
fn visit_i128(&mut self, v: i128) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
fn visit_f64(&mut self, v: f64) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
fn visit_bool(&mut self, v: bool) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
fn visit_char(&mut self, v: char) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
fn visit_str(&mut self, v: &str) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
fn visit_bytes(&mut self, v: &[u8]) -> Result<(), Error> {
self.visit_args(format_args!("{:?}", v))
}
fn visit_none(&mut self) -> Result<(), Error> {
self.visit_args(format_args!("None"))
}
fn visit_args(&mut self, args: &std::fmt::Arguments) -> Result<(), Error>;
}
// --- Concrete types for keys and values --- //
// `ToOwned::Owned: Send + Sync + 'static`?
// use `Borrow<str>` as the generic trait bound for keys
pub struct Key<'k> {}
impl<'k> Key<'k> {
fn as_str(&self) -> &str {}
}
// `Key` is always `Visit`
// If `serde` is available, it's also `Serialize`
#[cfg(not(feature = "serde"))]
impl<'k> Visit for Key<'k> {}
#[cfg(feature = "serde")]
impl<'k> Serialize for Key<'k> {}
// `ToOwned::Owned: Send + Sync + 'static`?
// use `Visit` as the generic trait bound for values
pub struct Value<'v> {}
// `Value` is always `Visit`
// If `serde` is available, it's also `Serialize`
#[cfg(not(feature = "serde"))]
impl<'v> Visit for Value<'v> {}
#[cfg(feature = "serde")]
impl<'v> Serialize for Value<'v> {}
impl<'v> Debug for Value<'v> {}That way a library implementing |
KodrAus
force-pushed the
KodrAus:rfc/structured-logging
branch
from
06ee40f
to
b818223
Oct 28, 2018
This comment has been minimized.
This comment has been minimized.
|
Alrighty, I've gone ahead and updated the RFC based on the feedback so far. It removes I've also added a section on how we could manage getting an owned version of the key-value pairs that is If you'd rather poke around an example implementation, I've got one here |
Thomasdezeeuw
reviewed
Oct 30, 2018
Thank you @KodrAus for writing this RFC.
I however don't think this is the right way forward for this crate. Aiming to be the standard logging crate means supporting a lot of use cases, while not growing too big. And I my opinion these additions make the crate too complex and too large, even when leaving out serde.
The number of types and trait required to support this doesn't seem to be worth it, to me atleast. Eventhough I use serde myself I also think that serde is too complex, it only gets away with it because it has a limited number of backends (e.g. serde_json) and has nice macro support to derive the required frontend traits.
However serde only implements one way to serialise data structures. Maybe a different way of generic serialisation, one that is easier to implement, should be the way forward.
| self.visit(visitor) | ||
| } | ||
| fn erased_get<'kvs>(&'kvs self, key: &str) -> Option<Value<'kvs>> { |
This comment has been minimized.
This comment has been minimized.
rfcs/0000-structured-logging.md Outdated
rfcs/0000-structured-logging.md Outdated
rfcs/0000-structured-logging.md Outdated
rfcs/0000-structured-logging.md Outdated
| Ok(()) | ||
| } | ||
| } | ||
| ``` |
This comment has been minimized.
This comment has been minimized.
Thomasdezeeuw
Oct 30, 2018
Contributor
All the code examples seem somewhat repetitive, any possibility of providing a derive macro? Like serde does.
This comment has been minimized.
This comment has been minimized.
KodrAus
Oct 31, 2018
Contributor
Ah these examples are all just variations of the same thing; implementing the Source trait where the underlying data is in various different shapes. I'm not sure a macro would be worthwhile for deriving Source, because the way you'd surface key-value pairs is really dependent on what the underlying source is.
| } | ||
| ``` | ||
|
|
||
| ### `Error` |
This comment has been minimized.
This comment has been minimized.
Thomasdezeeuw
Oct 30, 2018
Contributor
Do we really need a custom error? Is there any serialisation format that can fail? Can't this be relaced by io::Error?
This comment has been minimized.
This comment has been minimized.
KodrAus
Oct 31, 2018
Contributor
Json can fail if you give it non-string keys, and any printing done through the Write trait can also fail. We do need our own error type because the log crate needs to work in no-std environments, and io::Error is only available in the standard library.
rfcs/0000-structured-logging.md Outdated
rfcs/0000-structured-logging.md Outdated
This comment has been minimized.
This comment has been minimized.
|
@Thomasdezeeuw thanks for taking the time to read through this giant document! I appreciate your input on how readable it is(n't). Before working through those though maybe we should try address the main criticism you've raised. When talking about this API being too big are you talking about the surface area or the volume? I mean, do you think the public API is too big, or do you think there's too much going on behind the scenes to support it? Personally, I don't think the public API is too big here, but there's definitely a lot more going on than in the simplest API we could imagine for key-value pairs. If we look at the simplest possible representation we can see where the API proposed here abstracts over it: &[
("key1", 42),
("key2", &["a", "b", "c"]),
("key3": Uuid::new_v4()),
];There are two abstractions that we work with:
The ┌ &[
│ ("key1", 42),
│ ("key2", &["a", "b", "c"]),
│ ("key3": Uuid::new_v4()),
├ ];
└ `Source`This abstraction is important for being able to convert records from other frameworks to and from records in The &[
("key1", 42),
┌─────────────┴──┘
│ ("key2", &["a", "b", "c"]),
├─────────────┴────────────────┘
│ ("key3": Uuid::new_v4()),
├─────────────┴──────────────┘
│ ];
└ `Visit`This abstraction is important (over just something like The API proposed for So I appreciate the need to keep @sfackler do you have any more thoughts? |
This comment has been minimized.
This comment has been minimized.
|
@KodrAus Saying that the crate would become too big wasn't clear, your right. What I meant was that the public API would become too big, i.e. it would have too many types (struct/traits). If I counted correctly 7 new types would be added to the public API. On top of that another couple helpers, e.g. For a "simple" logging facade it doesn't seem so simple to me. But I do agree that structured logging is a fundamental feature. So perhaps with some structuring of the public API, e.g. by pushing most P.S. I hope I wasn't too harsh with my feedback. |
This comment has been minimized.
This comment has been minimized.
|
@KodrAus To continue the discussion around the Upside is that the public API remains small, adds only a single type. Downside is that the order of the iterator is now unspecified, I'm unsure if this is important for you. And we still have fully typed value (only when they fit in |
This comment has been minimized.
This comment has been minimized.
Hmm, I wouldn't necessarily describe
This is actually where I started earlier this year, but ran into some complications. The problem with an iterator approach is that it's not object safe, and making it object-safe requires some unappealing public machinery for progressing and resetting the iterator. So the The So I don't think just aiming to reduce the number of types is necessarily going to result in a more understandable public API. That lands us in a state where we make arbitrary limitations for the sake of an API we assume is more understandable. We want the API to be understandable through its use. I think the way to do that is to look at the interactions of these types for unnecessary complications, rather than just treating their existence as a complication. It is definitely worth making each type justify its existence though. |
This comment has been minimized.
This comment has been minimized.
|
To make it a little easier to explore, I've pushed up the API docs for this proposed API. |
KodrAus
added some commits
Nov 11, 2018
This comment has been minimized.
This comment has been minimized.
|
Working through the process of pushing Something like this would remove all the wackiness around changing trait bounds based on Cargo features in order to avoid loggable types depending on |
| "This is the rendered {message}. It is not structured", | ||
| message = "message"; | ||
| ^^^^^^^^^^^^^^^^^^^ | ||
| unstructured |
This comment has been minimized.
This comment has been minimized.
scottmcm
Nov 17, 2018
This surprised me. I'm use to (not in rust) doing messages like "action {action_name} completed in {duration}ms", and still getting the properties as real columns that I can filter and pivot on.
This comment has been minimized.
This comment has been minimized.
KodrAus
Nov 24, 2018
Contributor
Yes, that's a better way to do things, but we couldn't do this and maintain potential structure without potential breakage to callers of log!, because the trait bounds within that format would change.
Thomasdezeeuw
referenced this pull request
Nov 21, 2018
Closed
Initial implementation of adding key value pairs #281
This comment has been minimized.
This comment has been minimized.
|
Ok, I've got something more to share on the serialization front. This library (with rough source here) is similar to the last one I posted, but has a smaller surface area. It's no-std, object-safe and integrates optionally with For integrating an external serialization framework like this into
Personally, I'm thinking the third option could be worth pursuing, since callers would already not be able to log their Does anybody else have any thoughts? |
This comment has been minimized.
This comment has been minimized.
|
I've managed to convince myself that it's not super important for structured logging in So if we don't need to bake a framework in out-of-the-box because we don't need to capture its values out-of-the-box, then we can probably move forward with a much slimmer values API, and see what direction I was thinking a public API something like: pub trait ToValue {
fn to_value(&self) -> Value;
}
impl ToValue for u64 { .. }
impl ToValue for i64 { .. }
impl ToValue for str { .. }
// etc.
pub trait Visitor {
// The only required method
fn visit_any(&mut self, value: Value) -> Result<(), Error>;
// Provided methods for primitives
fn visit_u64(&mut self, value: u64) -> Result<(), Error> {
self.visit_any(value.to_value())
}
}
pub struct Value<'a>(_);
impl<'a> Value<'a> {
pub fn from_debug(v: &dyn fmt::Debug) -> Self { .. }
pub fn visit(&self, visitor: impl Visitor) -> Result<(), Error> { .. }
}
impl<'a> fmt::Debug for Value<'a> { .. }Internally, we can use private methods on As a provider of dataustructures, like As a consumer of structured logs like Later, to extend this system to support another serialization framework, like
For That way, any implementation for That could be a way for us to unblock on the serialization question. |
KodrAus commentedOct 14, 2018
•
edited
Rendered
Sample API docs
Hi! So we've been working through some designs in #149 on how structured logging could be supported in
log. I've since gone away and tried to capture the results of that discussion and my experiments over the year into a concrete proposal that we can refine further. It looks pretty long, but a fair chunk of that is blocks of source.I don't necessarily expect us to merge this PR even once we're happy with it, it's really just an effort to get more directed feedback on a significant API using a process we're familiar with in the Rust community.
cc @sfackler @dpc (who I know has limited capacity) @Thomasdezeeuw