-
Notifications
You must be signed in to change notification settings - Fork 896
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
OTEP: Logger.Enabled #4290
base: main
Are you sure you want to change the base?
OTEP: Logger.Enabled #4290
Conversation
This PR was marked stale due to lack of activity. It will be closed in 7 days. |
This PR was marked stale due to lack of activity. It will be closed in 7 days. |
oteps/logs/4290-logger-enabled.md
Outdated
2. Avoid allocating memory to store a log record, avoid performing computationally expensive operations and avoid exporting when emitting a log or event record is unnecessary. | ||
3. Configure a minimum a log serverity level on the SDK level. | ||
4. Filter out log and event records when they are not inside a recording span. | ||
5. Have **fine-grained** filtering control for logging pipelines without using an OpenTelemetry Collector (e.g. mobile devices, serverless, IoT). |
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.
One thing I am not so sure about.. Do we expect OTel SDK to provide such control, mimicking what is often provided by the Logging libraries themselves? They often provide sophisticated mechanisms, and it may not be worth replicating everything in OTel...
We can explicitly state that our desire is to provide some control, but not necessarily matching/competing with existing logging libraries?
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 do not expect the Logs API to be as user-friendly as the logging libraries popular for given language.
However, I think that the SDK (the logging backend) should be able handle different logging processing scenarios. E.g. sending verbose logs via user_events and warning logs via OTLP (probably I could come up with better examples). I heard that someone wanted to send non-event log records and event records to different destinations. Moreover, there is the following question in the Events Basics OTEP:
How to support routing logs from the Logs API to a language-specific logging library while simultaneously routing logs from the language-specific logging library to an OpenTelemetry Logging Exporter?
I see logging libraries as logs frontend and Logs SDK as logs backend. Logs API is the thing which can bridges between the frontend and backend.
Side note: I think that even without this use case we would get the same design.
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 moved it as the last use-case given that it may be seen as the least important. a4257ef
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.
Let me know if the current way it is described looks good overall.
is going to emit a log record. | ||
|
||
For (3), the user can declaratively configure the Logs SDK | ||
using `LoggerConfigurator` to set the `minimum_severity_level` |
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 we'll (eventually) have more config options for severity, sampling, and other things, e.g. a few extra scenarios I can think of:
- minimum level for this event name
- maximum level to sample at (e.g. you only want to sample info and lower)
- minimum level to record exception stack traces at
- sampling on/off rate for this event name
- sample based on spans, but also record all logs without parent span (e.g. startup logs are quite important)
- throttling config (record this event name, but at most X times per time unit)
- ...
We'll need to think how to structure them on LoggerConfig
API so it's extendable but only introduce options we have an immediate need for).
E.g. I think we may need something like LoggerConfig.sampling_strategy = always_on | span_based
instead of disabled_on_sampled_out_spans
.
Also we'd need to consider having a callback in addition to pure declarative config (e.g. for cases like sampling based on spans, but also startup logs are all in). For this we should consider having a sampler-like interface which would have implementations similar to tracer sampler and then we might want to steal declarative config from tracer_provider.sampler
.
TL;DR:
- let's figure out what we should do first - declarative config vs api extensibility
- let's come up with extendable options
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.
We'll need to think how to structure them on LoggerConfig API so it's extendable but only introduce options we have an immediate need for).
Adding a field is a backwards compatible change. I agree that we should add new fields (options) only if we have a big demand for it.
we'd need to consider having a callback in addition to pure declarative config
For more ad I think the LogRecordProcessor
comes into play. It should handle the most advanced scenarios.
I will do my best to describe it "Future possibilities" section.
let's figure out what we should do first - declarative config vs api extensibility
IMO extensibility (LogRecordProcessor.Enabled
) is more important as it can also solve the (3) and (4) use cases even without the config extensions.
let's come up with extendable options
I would like to know if there is a use cases that the proposal of adding LogRecordProcessor.Enabled
would not solve.
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.
Also we'd need to consider having a callback in addition to pure declarative config
I added "Alternatives: Dynamic Evaluation in LoggerConfig"
I think we'll (eventually) have more config options for severity, sampling, and other things, e.g. a few extra scenarios I can think of:
I added "Future possibilities: Extending LoggerConfig
We would need to closer look at the alternatives when prototyping and during the Development phase of the spec.
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.
@lmolkova, is there anything more to address regarding this discussion?
|
||
processors := l.provider.processors() | ||
for _, p := range processors { | ||
if p.Enabled(ctx, param) { |
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.
what would you expect processors to do inside Enabled
? If this is a delegating processor, would it call the underlying one? When I'm implementing a processor that does filtering, I should assume it's in the certain position in the pipeline?
I.e. it sounds like there should be just one processor in the pipeline that decides if log should be created. If so, should it be a processor responsibility at all or can we call this component LogFilter
, LogSampler
, etc and make it a configurable behavior independent of the processor?
If this component returns true, all processing pipelines would get log record and can drop it if they are filtering too.
If it returns false, none of them should. This is the same as you have outlined in the otep, just extracts filtering from processing.
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.
Some examples of logging filters:
Here's a good old .NET ILogger filter
builder.Logging.AddFilter((provider, category, logLevel) =>
{
if (provider.Contains("ConsoleLoggerProvider")
&& category.Contains("Controller")
&& logLevel >= LogLevel.Information)
{
return true;
}
else if (provider.Contains("ConsoleLoggerProvider")
&& category.Contains("Microsoft")
&& logLevel >= LogLevel.Information)
{
return true;
}
else
{
return false;
}
});
or good old log4j
private void updateLoggers(final Appender appender, final Configuration config) {
final Level level = null;
final Filter filter = null;
for (final LoggerConfig loggerConfig : config.getLoggers().values()) {
loggerConfig.addAppender(appender, level, filter);
}
config.getRootLogger().addAppender(appender, level, filter);
}
I.e. composition instead of inheritance - when the logging is configured, each appender/provider pipeline gets one dedicated filter independent of the appender/provider logic.
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.
what would you expect processors to do inside
Enabled
? If this is a delegating processor, would it call the underlying one? When I'm implementing a processor that does filtering, I should assume it's in the certain position in the pipeline?
Depends on the processor.
- If this is an exporting processor, e.g. for user_events then
Enabled
, returns results only for itself. Example: https://github.com/open-telemetry/opentelemetry-rust-contrib/blob/30a3f4eeac0be5e4337f020b7e5bfe0273298858/opentelemetry-user-events-logs/src/logs/exporter.rs#L158-L165. Use case (5). - For a filtering processor it needs to wrap some other processor for which it applies the filtering. Then it makes it possible to have two batching processors with different filters. Example: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/a5391050f37752bbcb62140ead0301981af9d748/processors/minsev/minsev.go#L74-L84 Use case (6).
I.e. it sounds like there should be just one processor in the pipeline that decides if log should be created. If so, should it be a processor responsibility at all or can we call this component
LogFilter
,LogSampler
, etc and make it a configurable behavior independent of the processor?If this component returns true, all processing pipelines would get log record and can drop it if they are filtering too. If it returns false, none of them should. This is the same as you have outlined in the otep, just extracts filtering from processing.
I think is that it does not suites well (5) nor (6) use cases.
For (5) would someone configuring an user_events exporter would have to configure a filterer as well as processor?
For (6) it would not be even possible to e.g. allowing distinct minimum severity levels for different processors as the filters would run before the processors.
The other way is that the processors themselves would accept a filterer, but I do not think we want to Logs SDK to become https://logging.apache.org/log4j/2.x/manual/architecture.html (which by the way does not offer an Enabled
API).
I.e. composition instead of inheritance - when the logging is configured, each appender/provider pipeline gets one dedicated filter independent of the appender/provider logic.
Filtering processor mentioned before wraps an existing processor so it is following the principle to favor composition over inheritance.
I added "Alternatives: Separate LogRecordFilterer Abstraction".
We would need to closer look at the alternatives when prototyping and during the Development phase of the spec.
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.
For (5) would someone configuring an user_events exporter would have to configure a filterer as well as processor?
The trade-off:
- most processors would not need
IsEnabled
- processors today are for processing LogRecords. The two processor we require (simple and batch) would not have it. IfIsEnabled
is an API on the processor, from now on everyone would need to think twice whether they need to implement it and what would delegating/composite processors need to do ("any of" or "all of"?) - a few processors (it's really exporters, not processors) that would need
IsEnabled
would have a bit more complicated config.
I would pick 2 as the better option.
For (6) it would not be even possible to e.g. allowing distinct minimum severity levels for different processors as the filters would run before the processors.
The filter can be "any of" (if any of the filters returns true, record is passed to the processor) .
Processor implementations can absolutely support additional filtering, but not through base interface API.
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.
most processors would not need
IsEnabled
Similarly most exporters do not need ForceFlush
and Shutdown
.
If it is "not needed" then it should return true
. It could be the default implementation (this is how ForceFlush
and Shutdown
is designed in some languages). I think that the current proposal follows existing patterns.
If IsEnabled is an API on the processor, from now on everyone would need to think twice whether they need to implement it and what would delegating/composite processors need to do
I think that in this context it is better that the developer would need to think how IsEnabled
should be implemented.
Processor implementations can absolutely support additional filtering, but not through base interface API.
Then each processor would have to provide its own filtering API. I think it would make the user experience worse.
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.
Can processors that need additional functionality implement both interfaces? Or is that not possible in all languages?
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.
Can processors that need additional functionality implement both interfaces? Or is that not possible in all languages?
Do you mean something that is currently described in "Trade-offs and mitigations" section?
For some langagues extending the
LogRecordProcessor
may be seen as breaking.
For these languages implementingLogRecordProcessor.Enabled
must be optional.
The SDKLogRecordProcessor
must returntrue
ifEnabled
is not implemented.
This approach is currently taken by OpenTelemetry Go.
Is it what you have in mind?
If so then do you think it should be the recommended approach?
I would say that it is a language/SIG specific decision because of the trade-offs of either approaches.
On the other hand, based on the feedback, I think that we should indeed try to coin define a new filterer abstraction (interface) that could be used as an option for the logger provider and processors. Defining it as a separate abstraction may result in better user experience in most scenarios.
My plan is to revisit, refresh and enhance a prototype I did some time ago: open-telemetry/opentelemetry-go#5825
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.
After some thoughts and chat with @lmolkova, I think we can start with LogRecordProcessor.Enabled
.
I still plan to create an issue for "Separate LogRecordFilterer Abstraction". If we will get feedback that LogRecordProcessor.Enabled
is bad then we can switch to it. I can still make a prototype for the alternative design. We can always revisit the design during Development phase before going stable after gathering feedback and more hand-on experience.
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 did my best to address it via 1a977c6
170fb68
to
48c1e11
Compare
The main purpose of this OTEP is to have an agreement that the SDK needs both:
Logger.Enabled
for customization and flexibility via extendingLogRecordProcessor
withEnabled
methodLoggerConfig
fields to conveniently address the most popular, especially configuring the minimum severity levelThe OTEP tries to elaborate how/why it should work well with (trace) sampling as well as Events API.
Fixes #4207