Skip to content
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

Add ECS-reformatting OVERRIDE #1793

Merged
merged 13 commits into from
May 4, 2021

Conversation

eyalkoren
Copy link
Contributor

@eyalkoren eyalkoren commented Apr 28, 2021

What does this PR do?

Adds the ability to OVERRIDE original logs with ECS-formatted events. As opposed to SHADE and REPLACE options, this is not limited only to log files, but to additional logging options. Most importantly, it allows to reformat System.out logs to ECS, thus enabling native Docker/k8s support.

Mechanism specifications:

  • Supports dynamic configuration of the log_ecs_reformatting config
  • Guarantees that each log event is handled exactly once
  • ECS shade/replace appenders should be created lazily only if and when configured, in order to avoid the creation of the ecs.json file where not required
  • Advice calls from the logging framework should be very efficient, unless when the configuration changes and resources need to be created per appender.
  • A config option allows to disable override/shade based on the underlying framework formatter type (where formatter is framework-specific, e.g Layout in log4j or Encoder in Logback)

Checklist

  • I have updated CHANGELOG.asciidoc
  • I have added tests that prove the specifications above are maintained
  • I have tested manually with log4j1, log4j2 and Logback
  • I have made corresponding changes to the documentation
  • I have implemented the new config option
  • I have updated the Log Onboarding spec with required changes/additions (e.g. new config option).

@apmmachine
Copy link
Contributor

apmmachine commented Apr 28, 2021

💚 Build Succeeded

the below badges are clickable and redirect to their specific view in the CI or DOCS
Pipeline View Test View Changes Artifacts preview

Expand to view the summary

Build stats

  • Build Cause: Pull request #1793 updated

  • Start Time: 2021-05-04T12:24:06.388+0000

  • Duration: 59 min 26 sec

  • Commit: 67d15b6

Test stats 🧪

Test Results
Failed 0
Passed 2004
Skipped 19
Total 2023

Trends 🧪

Image of Build Times

Image of Tests

💚 Flaky test report

Tests succeeded.

Expand to view the summary

Test stats 🧪

Test Results
Failed 0
Passed 2004
Skipped 19
Total 2023

@eyalkoren eyalkoren changed the title Add ECS-reformatting OVERRIDE option to log4j2 Add ECS-reformatting OVERRIDE May 2, 2021
@eyalkoren eyalkoren marked this pull request as ready for review May 2, 2021 08:53
@eyalkoren eyalkoren requested a review from SylvainJuge May 2, 2021 12:29
Copy link
Member

@felixbarny felixbarny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Just some minor stylistic comments.

* Used when {@link LoggingConfiguration#logEcsReformatting log_ecs_reformatting} is set to
* {@link LogEcsReformatting#SHADE SHADE} or {@link LogEcsReformatting#REPLACE REPLACE}.
*/
private static final WeakConcurrentMap<Object, Object> originalAppender2ecsAppender = WeakMapSupplier.createMap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are all maps static?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A habit 🙂
This guarantees there are no duplication, no matter where and how many helpers are created. Conceptually, everything here could be static, but with the maps this may actually have an effect if not.
Any reason to make them non-static? Are you worried from higher contention or something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they weren't static, we could use generics WeakConcurrentMap<A, A>/WeakConcurrentMap<A, R> which could make things a bit more readable. As we already have a static singleton instance, I was just a bit confused why the maps need to be shared across the different implementations.
That's not a blocker though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, but since I use dummy formatter and appender to optimize lookups that can't or shouldn't result with real implementations, switching to generic maps means adding the complication of creating these dummies in the helper subclasses. I like the fact that it is hidden from the sub-helpers, but I can try this out

* Therefore, by instrumenting this method and replacing the returned {@link Layout}, we can implement the
* {@link co.elastic.apm.agent.logging.LogEcsReformatting#OVERRIDE OVERRIDE} use case.
*/
public class Log4j2AppenderGetLayoutAdvice {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should agree on where to put advices. Either as static inner classes or as stand-alone classes.

I'd prefer the static inner class approach a lot. It makes instrumentations more cohesive as it's easier to grasp where an advice is applied to.

Copy link
Contributor Author

@eyalkoren eyalkoren May 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's much more readable this way, the thing is that it's much easier to get it wrong and make the instrumentation dependent on library types. I fixed quite a few of those lately, and I think a main reason for that is allowing something in the import section, that is not allowed somewhere in the class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you thinking about automatically validating the imports? Or are you saying that it's just easier for the reviewer to verify that there are no invalid imports?

I just had an idea how we could verify that the instrumentation doesn't use invalid classes. In AbstractInstrumentationTest, we could create an isolated child class loader (parent=bootstrap) that loads each instrumentation class and calls the get*Matcher methods. That would result in linkage errors if library types are referenced.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you thinking about automatically validating the imports? Or are you saying that it's just easier for the reviewer to verify that there are no invalid imports?

Auto-validating the imports means looking at source, so probably not. My experience shows that it's easier for the author to add a reference to something that is already at the imports in the IDE, and easier for the reviewer to miss.

I just had an idea how we could verify that the instrumentation doesn't use invalid classes. In AbstractInstrumentationTest, we could create an isolated child class loader (parent=bootstrap) that loads each instrumentation class and calls the get*Matcher methods. That would result in linkage errors if library types are referenced.

Good idea! You mean a separate URL CL that knows the agent jar and is the child of the boot CL (since it's a unit test, we cannot rely on the boot CL to load agent classes)?
The only thing is that we will still rely only on tested JVMs, and may be exposed to differences in linkage in non tested ones, but should capture such issues very quickly in most cases.

// Effectively disables instrumentation to all database appenders
return null;
}
Layout<? extends Serializable> ecsLayout = Log4J2EcsReformattingHelper.instance().getEcsOverridingFormatterFor(thisAppender);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a static field for the helper.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(also applies to other similar places)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

return thisAppender instanceof FileAppender && eventObject instanceof ILoggingEvent &&
LogbackLogShadingHelper.instance().shouldSkipAppend((FileAppender<ILoggingEvent>) thisAppender);

return eventObject instanceof ILoggingEvent && LogbackEcsReformattingHelper.instance().onAppendEnter(thisAppender);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a pitty that we can't use @Local in indy advices. Propagating the config value from the enter to the exit advice that way would be neat. But ofc. the ThreadLocal also works fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wouldn't be enough in this case though, because in log4j2 we need to use it in a separate advice as well


import javax.annotation.Nullable;

class LogbackEcsReformattingHelper extends AbstractEcsReformattingHelper<OutputStreamAppender<ILoggingEvent>, Encoder<ILoggingEvent>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like how straightforward the implementations now are.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
The abstract helper contains some complexity, but not as bad as I feared and I am also satisfied with how library helpers ended up being all about library-specific-ECS-logging and the advices are minimal and almost identical.

@eyalkoren eyalkoren merged commit 49d0db5 into elastic:master May 4, 2021
@eyalkoren eyalkoren deleted the ecs-log-override-option branch May 4, 2021 13:48
@AlexanderWert
Copy link
Member

Closes #1617

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants