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

Feature/log appender per pipeline #11108

Closed

Conversation

andsel
Copy link
Contributor

@andsel andsel commented Sep 2, 2019

This PR is related to issue #10427 and introduce the separation of pipeline logs in separate appenders.
It leverage the log4j2 RoutingAppender (https://logging.apache.org/log4j/2.x/manual/appenders.html#RoutingAppender), by default it's switched off and could be enabled with the config flag --pipeline.separate_logs=true/false. To disable this feature it's used a custom PropertiesConfigFactory that simply remove the routing appender at startup phase if it's switched off. It expecte to find a routing appender named pipeline_routing_appender

@andsel
Copy link
Contributor Author

andsel commented Sep 2, 2019

Jenkins test this please

@andsel andsel force-pushed the feature/log_appender_per_pipeline branch from e4dd715 to 2a9ed3c Compare September 3, 2019 15:54
@andsel andsel changed the title [WIP] Feature/log appender per pipeline Feature/log appender per pipeline Sep 3, 2019
Copy link
Member

@robbavey robbavey left a comment

Choose a reason for hiding this comment

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

I've done some initial testing on this, and confirmed that log entries are added to separate per-pipeline log files if enabled.

Most of my remarks are around tidying up the config, along with a question over duplication of log entries across per-pipeline logs and the main logstash log, and whether that should be required, optional or not done..

appender.routing.routes.route_pipelines.rolling.layout.type = PatternLayout
appender.routing.routes.route_pipelines.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
appender.routing.routes.route_pipelines.rolling.policy.type = SizeBasedTriggeringPolicy
appender.routing.routes.route_pipelines.rolling.policy.size = 500
Copy link
Member

Choose a reason for hiding this comment

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

Needs to be increased before commiting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, I'll bring the same configs used for the main log

appender.routing.routes.script.type = Script
appender.routing.routes.script.name = routing_script
appender.routing.routes.script.language = JavaScript
appender.routing.routes.script.value = logEvent.getContextData().containsKey("pipeline.id") ? logEvent.getContextMap().get("pipeline.id") : "sink";
Copy link
Member

Choose a reason for hiding this comment

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

Any reason why getContextData and (deprecated) getContextMap are both in use here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

getContextMap was a cut/paste not changed to getContextData

import java.util.Properties;

@Plugin(name = "LogstashConfigurationFactory", category = ConfigurationFactory.CATEGORY)
@Order(9)
Copy link
Member

Choose a reason for hiding this comment

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

What is the significance of 9?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the order that Log4J2 uses to load the plugins. The standard log4 PropertiesConfigurationFactory has Order = 8, higher means more priority

appender.routing.routes.route_pipelines.rolling.fileName = ${sys:ls.logs}/pipeline_${ctx:pipeline.id}.log
appender.routing.routes.route_pipelines.rolling.filePattern = ${sys:ls.logs}/pipeline_${ctx:pipeline.id}.%i.log.gz
appender.routing.routes.route_pipelines.rolling.layout.type = PatternLayout
appender.routing.routes.route_pipelines.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
Copy link
Member

Choose a reason for hiding this comment

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

We should probably try and keep the formatting consistent with that used in other log entries, something like:

[%d{ISO8601}][%-5p][%-25c] %m%n

(although I'm not necessarily averse to introducing the thread to the log)

appender.routing.routes.route_pipelines.rolling.filePattern = ${sys:ls.logs}/pipeline_${ctx:pipeline.id}.%i.log.gz
appender.routing.routes.route_pipelines.rolling.layout.type = PatternLayout
appender.routing.routes.route_pipelines.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
appender.routing.routes.route_pipelines.rolling.policy.type = SizeBasedTriggeringPolicy
Copy link
Member

Choose a reason for hiding this comment

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

Do we also need to setup a rollover strategy here?

@@ -44,6 +44,7 @@ module Environment
Setting::Boolean.new("pipeline.java_execution", true),
Setting::Boolean.new("pipeline.reloadable", true),
Setting::Boolean.new("pipeline.plugin_classloaders", false),
Setting::Boolean.new("pipeline.separate_logs", false),
Copy link
Member

Choose a reason for hiding this comment

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

I notice from testing, that we get log entries produced in the pipeline duplicated in the pipeline logs, and in the main log. This leads me to wonder:

How do we want to handle this:
a) Duplicate all per-pipeline log entries in the per-pipeline logs and the main log
b) Separate all per-pipeline log entries into the per-pipeline logs only.
c) Allow the user to choose main, per-pipeline or both for pipeline log entries.

Thoughts @jsvd, @andsel?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that if the log goes in the pipeline logs it shouldn't be present also in the main

Copy link
Member

Choose a reason for hiding this comment

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

++ to b), if you set separate_logs, then pipeline-specific log entries should not land in the main log file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed by commit 1ad14ee

System.setProperty("ls.logs", "build/logs");
System.setProperty(LogstashConfigurationFactory.PIPELINE_SEPARATE_LOGS, "true");

ThreadContext.clearAll();
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to put this into a @after/tearDown to avoid the ThreadContext potentially polluting other tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean to to save the System.properties used + the ThreadContext in the @before and restore it in the @after? To me sounds good, at the end the unit test results as side effect free to the System.properties

Copy link
Member

Choose a reason for hiding this comment

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

Yes, something like that to avoid the test having any unforeseen side effects - we might even be able to use @BeforeClass and @AfterClass
to set and unset the properties, and an @Rule to handle the ThreadContext -

 @Rule
    public ThreadContextRule threadContextRule = new ThreadContextRule();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I do it, I didn't used the @Rule because in JUnit5 does not exists rules as I read, so be open moving to JUnit5 without incur in deprecated stuff.

Copy link
Member

Choose a reason for hiding this comment

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

Ah! I missed that @Rule was a junit5 thing. No worries!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed by 3ab931e

@robbavey robbavey mentioned this pull request Sep 5, 2019
8 tasks
@andsel
Copy link
Contributor Author

andsel commented Sep 5, 2019

Jenkins test this please

@andsel
Copy link
Contributor Author

andsel commented Sep 6, 2019

@robbavey this is ready to be reviewed

Copy link
Member

@robbavey robbavey left a comment

Choose a reason for hiding this comment

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

@andsel Couple of concerns - firstly running this on JDK 11 the following error message is emitted:

Warning: Nashorn engine is planned to be removed from a future JDK release

It is emitted every time a pipeline log entry is written if pipeline.separate_logs is set to true, less frequently if it is set to false

Presumably this is due to the use of javascript in the log4j2.properties file.

Secondly, when pipelines.separate_logs is set to true, log entries that are re-directed to separate log files no longer appear in the console logs. I think by default, we would still want to have those entries emitted via the console, and only separated in the log files. What do you think @jsvd?

appender.routing.routes.route1.type = Route
appender.routing.routes.route1.list.type = List
appender.routing.routes.route1.list.name = appender-${mdc:pipeline.id}
#appender.routing.routes.route1.rolling.type = RollingFile
Copy link
Member

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 before commit

@@ -38,10 +38,63 @@
}
IO.write(@ls.application_settings_file, settings.to_yaml)
@ls.spawn_logstash("-w", "1" , "-e", config)
@ls.wait_for_logstash
sleep 2 until @ls.exited?
#@ls.wait_for_logstash
Copy link
Member

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 before commit

}
IO.write(@ls.application_settings_file, settings.to_yaml)
@ls.spawn_logstash("-w", "1" , "-e", config)
#@ls.wait_for_logstash
Copy link
Member

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 before commit

@andsel
Copy link
Contributor Author

andsel commented Sep 9, 2019

@robbavey @jsvd to make the console appender continue printing the log lines also in case the -Dpipeline.separate_log=true it's a quick fix, matter of remove the filtering part from the console.

Regarding the Nashorn warning we could disable with a jvm.option -Dnashorn.args="--no-deprecation-warning" but we have to decide which scripting engine to use after JDK 12, do we need to include Nashorn manually or we change scripting to Groovy or maybe integrate Painless?

@robbavey
Copy link
Member

robbavey commented Sep 9, 2019

@andsel Personally, I think it makes sense to remove the filtering part from the console. And I think removing the Nashorn deprecation warning is ok for now.

It may even be possible to use jruby as a scripting engine, as it looks like log4j supports any JSR-223 scripting language, (https://github.com/jruby/jruby/wiki/Embedding-with-JSR-223) but I've a) not tested it, or b) seen anyone else doing it...

By the way, do you know if there is any sort of performance impact on introducing scripting into the logging routing?

@andsel
Copy link
Contributor Author

andsel commented Sep 10, 2019

@robbavey I've checked with JMH benchmark, and if the benchmark is correct, the output says that we loose not so much:

Result "org.logstash.benchmark.LogPerPipelineBenchmark.logWithoutScriptingCodeToExecute":
  737681.956 ±(99.9%) 129242.396 ops/ms [Average]
  (min, avg, max) = (554301.198, 737681.956, 824737.847), stdev = 85485.842
  CI (99.9%): [608439.561, 866924.352] (assumes normal distribution)


# Run complete. Total time: 00:00:03

Benchmark                                                  Mode  Cnt       Score        Error   Units
LogPerPipelineBenchmark.logWithScriptingCodeToExecute     thrpt   10  704964.359 ±  50375.222  ops/ms
LogPerPipelineBenchmark.logWithoutScriptingCodeToExecute  thrpt   10  737681.956 ± 129242.396  ops/ms

@andsel
Copy link
Contributor Author

andsel commented Sep 11, 2019

Jenkins test this please

Copy link
Member

@robbavey robbavey left a comment

Choose a reason for hiding this comment

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

Couple of questions on the benchmarks. Everything else is good, I think

@@ -45,6 +45,10 @@ dependencies {
compile 'commons-io:commons-io:2.5'
runtime 'joda-time:joda-time:2.8.2'
compile "org.jruby:jruby-core:$jrubyVersion"
// compile 'org.apache.logging.log4j:log4j-api:2.11.1'
Copy link
Member

Choose a reason for hiding this comment

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

Why did these need to be commented out?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was a try to fix a console log in benchmark.
Launch the benchmark with ./gradlew jmh -Pinclude="org.logstash.benchmark.LogPerPipelineBenchmark.*"

On the console I've many lines like this:

ERROR StatusLogger Unrecognized conversion specifier [d] starting at position 16 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [thread]
ERROR StatusLogger Unrecognized conversion specifier [thread] starting at position 25 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [level]
ERROR StatusLogger Unrecognized conversion specifier [level] starting at position 35 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [logger]
ERROR StatusLogger Unrecognized conversion specifier [logger] starting at position 47 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [msg]
ERROR StatusLogger Unrecognized conversion specifier [msg] starting at position 54 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [n]
ERROR StatusLogger Unrecognized conversion specifier [n] starting at position 56 in conversion pattern.
2019-09-13 10:49:14,358 org.logstash.benchmark.LogPerPipelineBenchmark.logWithScriptingCodeToExecute-jmh-worker-1 ERROR Unable to locate plugin type for Loggers
2019-09-13 10:49:14,363 org.logstash.benchmark.LogPerPipelineBenchmark.logWithScriptingCodeToExecute-jmh-worker-1 ERROR Unable to locate plugin type for Appenders

And I don't know why


@Benchmark
@OperationsPerInvocation(EVENTS_PER_INVOCATION)
public final void logWithoutScriptingCodeToExecute() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: These two tests are basically the same test with only the System property changing, so redundant code can be removed.

#appender.console.avoid_pipelined_filter.script.type = Script
#appender.console.avoid_pipelined_filter.script.name = filter_no_pipelined
#appender.console.avoid_pipelined_filter.script.language = JavaScript
#appender.console.avoid_pipelined_filter.script.value = ${sys:ls.pipeline.separate_logs} == false || !(logEvent.getContextData().containsKey("pipeline.id"))
Copy link
Member

Choose a reason for hiding this comment

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

Does uncommenting this block to enable the filter have any effect on the performance?

I think there might be 3 tests - no scripting in log4j files, scripting with separate_logs = false and scripting with separate_logs = true.

@andsel andsel force-pushed the feature/log_appender_per_pipeline branch 2 times, most recently from 574dba9 to 67ba711 Compare September 13, 2019 10:08
@andsel andsel force-pushed the feature/log_appender_per_pipeline branch from 67ba711 to 356cef2 Compare September 20, 2019 07:22
@andsel andsel force-pushed the feature/log_appender_per_pipeline branch from 0d41698 to cce5431 Compare September 30, 2019 08:55
Copy link
Member

@robbavey robbavey left a comment

Choose a reason for hiding this comment

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

One issue with the nashorn args in jvm.options

@@ -79,3 +79,6 @@

# Copy the logging context from parent threads to children
-Dlog4j2.isThreadContextMapInheritable=true

# Avoid Nashorn deprecation logs in JDK > 11
-Dnashorn.args="--no-deprecation-warning"
Copy link
Member

Choose a reason for hiding this comment

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

Testing this locally did not work with the quotes in place, but did without

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, I was testing with flag passed directly in my IDE

@andsel andsel force-pushed the feature/log_appender_per_pipeline branch from cce5431 to c02fcb1 Compare October 3, 2019 09:35
@andsel andsel requested a review from robbavey October 7, 2019 13:08
@andsel andsel force-pushed the feature/log_appender_per_pipeline branch from c02fcb1 to cce5431 Compare October 7, 2019 15:46
Copy link
Member

@robbavey robbavey left a comment

Choose a reason for hiding this comment

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

@andsel One more thing! Can we add a commented out section to logstash.yml explaining the new setting?

Copy link
Member

@robbavey robbavey left a comment

Choose a reason for hiding this comment

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

LGTM. Can you please merge this into master and 7.x

…logs per pipelines

- use log4j RoutingAppender
- avoid output to main log files when log per pipeline is enabled
- closes 10427
@andsel andsel force-pushed the feature/log_appender_per_pipeline branch from ae8fdf9 to 3028c37 Compare October 8, 2019 14:03
@elasticsearch-bot
Copy link

Andrea Selva merged this into the following branches!

Branch Commits
master e58a6e0
7.x fc9c0e0

elasticsearch-bot pushed a commit that referenced this pull request Oct 8, 2019
…logs per pipelines - use log4j RoutingAppender - avoid output to main log files when log per pipeline is enabled - closes 10427

Fixes #11108
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.

None yet

4 participants