-
Notifications
You must be signed in to change notification settings - Fork 326
Add support for JUL auto ECS-reformatting #2591
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
Changes from all commits
1c3ca8f
71d8285
594ca89
3bef40c
b5fc1a8
12e495e
9528eda
61ed57d
36a648a
a50b9db
e4b9042
22dfe82
d412a65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <parent> | ||
| <artifactId>apm-logging-plugin</artifactId> | ||
| <groupId>co.elastic.apm</groupId> | ||
| <version>1.30.2-SNAPSHOT</version> | ||
| </parent> | ||
|
|
||
| <artifactId>apm-jul-plugin</artifactId> | ||
| <name>${project.groupId}:${project.artifactId}</name> | ||
|
|
||
| <properties> | ||
| <apm-agent-parent.base.dir>${project.basedir}/../../..</apm-agent-parent.base.dir> | ||
| </properties> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>${project.groupId}</groupId> | ||
| <artifactId>apm-logging-plugin-common</artifactId> | ||
| <version>${project.version}</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>co.elastic.logging</groupId> | ||
| <artifactId>jul-ecs-formatter</artifactId> | ||
| <version>${version.ecs.logging}</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>${project.groupId}</groupId> | ||
| <artifactId>apm-logging-plugin-common</artifactId> | ||
| <version>${project.version}</version> | ||
| <type>test-jar</type> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| <build> | ||
| <plugins> | ||
| <plugin> | ||
| <artifactId>maven-surefire-plugin</artifactId> | ||
| <configuration> | ||
| <!-- adds the ability to use reflection to inspect java.util.logging classes --> | ||
| <argLine>--add-opens java.logging/java.util.logging=ALL-UNNAMED</argLine> | ||
| </configuration> | ||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
| </project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /* | ||
| * Licensed to Elasticsearch B.V. under one or more contributor | ||
| * license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright | ||
| * ownership. Elasticsearch B.V. licenses this file to you under | ||
| * the Apache License, Version 2.0 (the "License"); you may | ||
| * not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package co.elastic.apm.agent.jul; | ||
|
|
||
| import co.elastic.apm.agent.loginstr.LoggingPluginClassLoaderRootPackageCustomizer; | ||
|
|
||
| public class JulPluginClassLoaderRootPackageCustomizer extends LoggingPluginClassLoaderRootPackageCustomizer {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /* | ||
| * Licensed to Elasticsearch B.V. under one or more contributor | ||
| * license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright | ||
| * ownership. Elasticsearch B.V. licenses this file to you under | ||
| * the Apache License, Version 2.0 (the "License"); you may | ||
| * not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| @NonnullApi | ||
| package co.elastic.apm.agent.jul.error; | ||
|
|
||
| import co.elastic.apm.agent.sdk.NonnullApi; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /* | ||
| * Licensed to Elasticsearch B.V. under one or more contributor | ||
| * license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright | ||
| * ownership. Elasticsearch B.V. licenses this file to you under | ||
| * the Apache License, Version 2.0 (the "License"); you may | ||
| * not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package co.elastic.apm.agent.jul.reformatting; | ||
|
|
||
| import net.bytebuddy.asm.Advice; | ||
| import net.bytebuddy.implementation.bytecode.assign.Assigner; | ||
|
|
||
| import java.util.logging.ConsoleHandler; | ||
| import java.util.logging.LogRecord; | ||
| import java.util.logging.StreamHandler; | ||
|
|
||
| public class JulConsoleHandlerPublishAdvice { | ||
|
|
||
| private static final JulEcsReformattingHelper helper = new JulEcsReformattingHelper(); | ||
|
|
||
| @SuppressWarnings("unused") | ||
| @Advice.OnMethodEnter(suppress = Throwable.class, skipOn = Advice.OnNonDefaultValue.class, inline = false) | ||
| public static boolean initializeReformatting(@Advice.This(typing = Assigner.Typing.DYNAMIC) ConsoleHandler thisHandler) { | ||
| return helper.onAppendEnter(thisHandler); | ||
| } | ||
|
|
||
| @SuppressWarnings({"unused"}) | ||
| @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false) | ||
| public static void reformatLoggingEvent(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) final LogRecord logRecord, | ||
| @Advice.This(typing = Assigner.Typing.DYNAMIC) ConsoleHandler thisHandler) { | ||
|
|
||
| StreamHandler shadeAppender = helper.onAppendExit(thisHandler); | ||
| if (shadeAppender != null) { | ||
| shadeAppender.publish(logRecord); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| /* | ||
| * Licensed to Elasticsearch B.V. under one or more contributor | ||
| * license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright | ||
| * ownership. Elasticsearch B.V. licenses this file to you under | ||
| * the Apache License, Version 2.0 (the "License"); you may | ||
| * not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package co.elastic.apm.agent.jul.reformatting; | ||
|
|
||
| import co.elastic.apm.agent.loginstr.reformatting.AbstractEcsReformattingHelper; | ||
| import co.elastic.apm.agent.loginstr.reformatting.Utils; | ||
| import co.elastic.apm.agent.sdk.logging.Logger; | ||
| import co.elastic.apm.agent.sdk.logging.LoggerFactory; | ||
| import co.elastic.apm.agent.util.LoggerUtils; | ||
| import co.elastic.logging.AdditionalField; | ||
| import co.elastic.logging.jul.EcsFormatter; | ||
|
|
||
| import javax.annotation.Nullable; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.logging.ConsoleHandler; | ||
| import java.util.logging.FileHandler; | ||
| import java.util.logging.Formatter; | ||
| import java.util.logging.StreamHandler; | ||
|
|
||
| class JulEcsReformattingHelper extends AbstractEcsReformattingHelper<StreamHandler, Formatter> { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(JulEcsReformattingHelper.class); | ||
| private static final Logger oneTimeLogFileLimitWarningLogger = LoggerUtils.logOnce(logger); | ||
|
|
||
| private static final ThreadLocal<String> currentPattern = new ThreadLocal<>(); | ||
| private static final ThreadLocal<Path> currentExampleLogFile = new ThreadLocal<>(); | ||
|
|
||
| JulEcsReformattingHelper() {} | ||
|
|
||
| public boolean onAppendEnter(FileHandler fileHandler, String pattern, File exampleLogFile) { | ||
| try { | ||
| currentPattern.set(pattern); | ||
| currentExampleLogFile.set(exampleLogFile.toPath()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [question] here we get the "first file" from the implementation internal state, how do we know that it's representative of all the files that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you look at |
||
| return super.onAppendEnter(fileHandler); | ||
| } finally { | ||
| currentPattern.remove(); | ||
| currentExampleLogFile.remove(); | ||
| } | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| protected Formatter getFormatterFrom(StreamHandler handler) { | ||
| return handler.getFormatter(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void setFormatter(StreamHandler handler, Formatter formatter) { | ||
| handler.setFormatter(formatter); | ||
| } | ||
|
|
||
| @Override | ||
| protected String getAppenderName(StreamHandler handler) { | ||
| if (handler instanceof FileHandler) { | ||
| return "FILE"; | ||
| } else if (handler instanceof ConsoleHandler) { | ||
| return "CONSOLE"; | ||
| } else { | ||
| return handler.getClass().getSimpleName(); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected Formatter createEcsFormatter(String eventDataset, @Nullable String serviceName, @Nullable String serviceVersion, | ||
| @Nullable String serviceNodeName, @Nullable Map<String, String> additionalFields, | ||
| Formatter originalFormatter) { | ||
| EcsFormatter ecsFormatter = new EcsFormatter(); | ||
| ecsFormatter.setServiceName(serviceName); | ||
| ecsFormatter.setServiceVersion(serviceVersion); | ||
| ecsFormatter.setServiceNodeName(serviceNodeName); | ||
| ecsFormatter.setEventDataset(eventDataset); | ||
| if (additionalFields != null && !additionalFields.isEmpty()) { | ||
| List<AdditionalField> additionalFieldList = new ArrayList<>(); | ||
| for (Map.Entry<String, String> keyValuePair : additionalFields.entrySet()) { | ||
| additionalFieldList.add(new AdditionalField(keyValuePair.getKey(), keyValuePair.getValue())); | ||
| } | ||
| ecsFormatter.setAdditionalFields(additionalFieldList); | ||
| } | ||
| ecsFormatter.setIncludeOrigin(false); | ||
| ecsFormatter.setStackTraceAsArray(false); | ||
| return ecsFormatter; | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| protected StreamHandler createAndStartEcsAppender(StreamHandler originalHandler, String ecsAppenderName, Formatter ecsFormatter) { | ||
| StreamHandler shadeHandler = null; | ||
| if (originalHandler instanceof FileHandler) { | ||
| try { | ||
| String pattern = computeEcsFileHandlerPattern( | ||
| currentPattern.get(), | ||
| currentExampleLogFile.get(), | ||
| getConfiguredReformattingDir(), | ||
| true | ||
| ); | ||
| // In earlier versions, there is only constructor with log file limit given as int, whereas in later ones there are | ||
| // overloads for both either int or long. Typically, this should be enough, but not necessarily | ||
| int maxLogFileSize = (int) getMaxLogFileSize(); | ||
| if ((long) maxLogFileSize != getMaxLogFileSize()) { | ||
| maxLogFileSize = (int) getDefaultMaxLogFileSize(); | ||
| oneTimeLogFileLimitWarningLogger.warn("Configured log max size ({} bytes) is too big for JUL settings, which " + | ||
| "use int to configure the file size limit. Consider reducing the log max size configuration to a value below " + | ||
| "Integer#MAX_VALUE. Defaulting to {} bytes.", getMaxLogFileSize(), maxLogFileSize); | ||
| } | ||
| shadeHandler = new FileHandler(pattern, maxLogFileSize, 2, true); | ||
| shadeHandler.setFormatter(ecsFormatter); | ||
| } catch (Exception e) { | ||
| logger.error("Failed to create Log shading FileAppender. Auto ECS reformatting will not work.", e); | ||
| } | ||
| } | ||
| return shadeHandler; | ||
| } | ||
|
|
||
| static String computeEcsFileHandlerPattern(String pattern, Path originalFilePath, @Nullable String configuredReformattingDir, | ||
| boolean createDirs) throws IOException { | ||
| pattern = Utils.replaceFileExtensionToEcsJson(pattern); | ||
| // if the pattern does not contain rotation component, append one at the end | ||
| if (!pattern.contains("%g")) { | ||
| pattern = pattern + ".%g"; | ||
| } | ||
| int lastPathSeparatorIndex = pattern.lastIndexOf('/'); | ||
| if (lastPathSeparatorIndex > 0 && pattern.length() > lastPathSeparatorIndex) { | ||
| pattern = pattern.substring(lastPathSeparatorIndex + 1); | ||
| } | ||
| Path logReformattingDir = Utils.computeLogReformattingDir(originalFilePath, configuredReformattingDir); | ||
| if (logReformattingDir != null) { | ||
| if (createDirs && !Files.exists(logReformattingDir)) { | ||
| Files.createDirectories(logReformattingDir); | ||
| } | ||
| pattern = logReformattingDir.resolve(pattern).toString(); | ||
| } | ||
| return pattern; | ||
| } | ||
|
|
||
| @Override | ||
| protected void closeShadeAppender(StreamHandler shadeHandler) { | ||
| shadeHandler.close(); | ||
| } | ||
| } | ||
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.
[minor] is there any reason not to rely on
co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent#buildThreadLocalhere ? Also, it might be worth checking if we need to have those fields within an@GlobalState.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.
There is no need because the value in both cases is basic Java and the this instrumentation is using the bootstrap CL. It is in my todo list to add an asciidoc regarding
ThreadLocaland when to use which, it just doesn't get to the top of priorities still...This plugin instruments basic Java types loaded by the bootstrap CL. Why should we require it to be a
@GlobalState?