├── .github ├── FUNDING.yml └── workflows │ └── maven.yml ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CHANGELOG.md ├── COPYING.txt ├── README.md ├── layout-benchmark ├── benchmark.py ├── maven-version-rules.xml ├── results.html ├── results.json └── results.out ├── layout-demo ├── maven-version-rules.xml ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── vlkan │ │ └── log4j2 │ │ └── logstash │ │ └── layout │ │ └── demo │ │ └── LogstashLayoutDemo.java │ └── resources │ └── log4j2.xml ├── layout-fatjar ├── maven-version-rules.xml ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── vlkan │ └── log4j2 │ └── logstash │ └── layout │ └── LogstashLayoutFatJar.java ├── layout ├── maven-version-rules.xml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── vlkan │ │ │ └── log4j2 │ │ │ └── logstash │ │ │ └── layout │ │ │ ├── LogstashLayout.java │ │ │ ├── LogstashLayoutSerializationContext.java │ │ │ ├── LogstashLayoutSerializationContexts.java │ │ │ ├── resolver │ │ │ ├── ContextDataResolver.java │ │ │ ├── ContextDataResolverFactory.java │ │ │ ├── ContextStackResolver.java │ │ │ ├── ContextStackResolverFactory.java │ │ │ ├── EndOfBatchResolver.java │ │ │ ├── EndOfBatchResolverFactory.java │ │ │ ├── EventResolver.java │ │ │ ├── EventResolverContext.java │ │ │ ├── EventResolverFactories.java │ │ │ ├── EventResolverFactory.java │ │ │ ├── ExceptionInternalResolverFactory.java │ │ │ ├── ExceptionResolver.java │ │ │ ├── ExceptionResolverFactory.java │ │ │ ├── ExceptionRootCauseResolver.java │ │ │ ├── ExceptionRootCauseResolverFactory.java │ │ │ ├── LevelResolver.java │ │ │ ├── LevelResolverFactory.java │ │ │ ├── LoggerResolver.java │ │ │ ├── LoggerResolverFactory.java │ │ │ ├── MainMapResolver.java │ │ │ ├── MainMapResolverFactory.java │ │ │ ├── MapResolver.java │ │ │ ├── MapResolverFactory.java │ │ │ ├── MarkerResolver.java │ │ │ ├── MarkerResolverFactory.java │ │ │ ├── MessageResolver.java │ │ │ ├── MessageResolverFactory.java │ │ │ ├── SourceResolver.java │ │ │ ├── SourceResolverFactory.java │ │ │ ├── StackTraceElementObjectResolver.java │ │ │ ├── StackTraceElementObjectResolverContext.java │ │ │ ├── StackTraceElementObjectResolverFactories.java │ │ │ ├── StackTraceElementObjectResolverFactory.java │ │ │ ├── StackTraceObjectResolver.java │ │ │ ├── StackTraceResolver.java │ │ │ ├── StackTraceTextResolver.java │ │ │ ├── TemplateResolver.java │ │ │ ├── TemplateResolverContext.java │ │ │ ├── TemplateResolverFactory.java │ │ │ ├── TemplateResolvers.java │ │ │ ├── ThreadResolver.java │ │ │ ├── ThreadResolverFactory.java │ │ │ ├── TimestampResolver.java │ │ │ └── TimestampResolverFactory.java │ │ │ └── util │ │ │ ├── AutoCloseables.java │ │ │ ├── BufferedPrintWriter.java │ │ │ ├── BufferedWriter.java │ │ │ ├── ByteBufferDestinations.java │ │ │ ├── ByteBufferOutputStream.java │ │ │ ├── JsonGenerators.java │ │ │ ├── Throwables.java │ │ │ └── Uris.java │ └── resources │ │ ├── EcsLayout.json │ │ ├── GelfLayout.json │ │ ├── Log4j2StackTraceElementLayout.json │ │ └── LogstashJsonEventLayoutV1.json │ └── test │ ├── java │ └── com │ │ └── vlkan │ │ └── log4j2 │ │ └── logstash │ │ └── layout │ │ ├── FixedByteBufferDestination.java │ │ ├── LogEventFixture.java │ │ ├── LogstashLayoutConcurrentEncodeTest.java │ │ ├── LogstashLayoutJsonGeneratorStateResetTest.java │ │ ├── LogstashLayoutTest.java │ │ ├── ObjectMapperFixture.java │ │ └── util │ │ └── JsonGeneratorsTest.java │ ├── perf │ └── com │ │ └── vlkan │ │ └── log4j2 │ │ └── logstash │ │ └── layout │ │ ├── BlackHoleByteBufferDestination.java │ │ ├── LogstashLayoutBenchmark.java │ │ └── LogstashLayoutBenchmarkState.java │ └── resources │ ├── Log4j2JsonLayout.json │ └── LogstashTestLayout.json ├── maven-version-rules.xml ├── mvnw ├── mvnw.cmd └── pom.xml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2022 Volkan Yazıcı 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permits and 13 | # limitations under the License. 14 | 15 | github: vy 16 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ${{ matrix.os }} 9 | 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macos-latest] 13 | 14 | steps: 15 | 16 | - name: Checkout repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup Maven caching 20 | uses: actions/cache@v2 21 | with: 22 | path: ~/.m2/repository 23 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 24 | restore-keys: | 25 | ${{ runner.os }}-maven- 26 | 27 | - name: Setup JDK 8 28 | uses: actions/setup-java@v1 29 | with: 30 | java-version: 8 31 | java-package: jdk 32 | architecture: x64 33 | 34 | - name: Build with Maven 35 | run: ./mvnw -V -B -e "-DtrimStackTrace=false" "-Dmaven.test.failure.ignore=true" "-Dsurefire.rerunFailingTestsCount=1" verify 36 | 37 | - name: Publish test results 38 | uses: scacap/action-surefire-report@v1 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | check_name: Test Report (${{ matrix.os }}) 42 | report_paths: '**/*-reports/TEST-*.xml' 43 | 44 | - name: Upload test reports 45 | uses: actions/upload-artifact@v2 46 | with: 47 | name: test-reports-${{ matrix.os }} 48 | path: '**/*-reports' 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea 3 | *.iml 4 | *.iws 5 | /out 6 | dependency-reduced-pom.xml 7 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vy/log4j2-logstash-layout/ebcb8a3ace28fc49df1f60b5962c42c5f3fff44d/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### (2020-10-30) v1.0.5 2 | 3 | - Fix Java 8 compatibility issues. (#69) 4 | 5 | ### (2020-10-29) v1.0.4 6 | 7 | - Try to dump the cause when stack trace serialization fails. 8 | 9 | ### (2020-06-30) v1.0.3 10 | 11 | - Fix handling of non-`String` values in `map` directive. (#61) 12 | 13 | ### (2020-03-18) v1.0.2 14 | 15 | - Fix `ArrayIndexOutOfBoundsException` thrown when `stackTrace:text` produces 16 | an output violating the truncation limit. (#57) 17 | 18 | - Implement work around for FasterXML/jackson-core#609 triggered when 19 | `maxStringLength` is used in combination with 20 | `emptyPropertyExclusionEnabled=true`. (#55) 21 | 22 | ### (2020-01-21) v1.0.1 23 | 24 | - Fix NPE in `StringTruncatingGeneratorDelegate`. (#53) 25 | 26 | ### (2020-01-08) v1.0.0 27 | 28 | - Switched to semantic versioning. 29 | 30 | - Add Java 9 module name: `com.vlkan.log4j2.logstash.layout`. 31 | 32 | - Set `maxByteCount` to 16 KiB by default. 33 | 34 | - Set `emptyPropertyExclusionEnabled` to `false` by default. 35 | 36 | - Remove `timestamp:nanos` and `timestamp:millis` directives in favor of 37 | `timestamp:epoch[:divisor=[,integral]]` directive. 38 | 39 | - Make formatted timestamp resolver GC-free. 40 | 41 | - Replace object pools with thread locals. 42 | 43 | - Add `locale` configuration. 44 | 45 | - Fix `JsonGenerator` state corruption in `ExceptionResolver` if 46 | `LogEvent#getThrown()` is `null`. 47 | 48 | - Add `GelfLayout.json` template. 49 | 50 | - Add `level:severity` and `level:severity:code` resolvers. (#48) 51 | 52 | - Add `timestamp:epoch:divisor=` resolver. (#48) 53 | 54 | ### (2019-10-15) v0.21 55 | 56 | - Add feature comparison matrix. 57 | 58 | - Update benchmarks. 59 | 60 | - Add ECS layout. (#39) 61 | 62 | - Add `char[]` caching for serializing stack traces. 63 | 64 | - Add serialization context caching. 65 | 66 | - Add `eventTemplateAdditionalFields` parameter. (#43) 67 | 68 | - Cache and reuse the most recently formatted timestamp. (#42) 69 | 70 | - Support user provided `ObjectMapper`s. (#35) 71 | 72 | - Support `SimpleMessage` in `${json:message:json}` directive. (#36) 73 | 74 | - Support `ObjectMessage` in `${json:message:json}` directive. (#36) 75 | 76 | ### (2019-08-09) v0.20 \[failed due to Sonatype mishap] 77 | 78 | - Use UTF-8 while serializing stack traces. 79 | 80 | ### (2019-06-13) v0.19 81 | 82 | - Add `maxStringLength` parameter enabling truncation of string fields. (#31) 83 | 84 | - Add `${json:map:xxx}` resolver for `MapLookup`s. (#33) 85 | 86 | - Fix the fallback to Log4j generic substitution for non-`${json:*}` templates. (#33) 87 | 88 | ### (2019-04-29) v0.18 89 | 90 | - Recover from corrupted `JsonGenerator` state after an exception. (#27) 91 | 92 | - Removed thread-locals, the plugin is not garbage-free anymore. It turned out 93 | to be pretty tricky to reuse a thread-local `JsonGenerator`. That said, the 94 | change almost had no performance impact. (#27, #29) 95 | 96 | ### (2019-04-08) v0.17 97 | 98 | - Make `LogstashLayout` backward compatible with Log4j 2.8. (#26) 99 | 100 | ### (2019-02-12) v0.16 101 | 102 | - Upgraded Jackson to 2.9.8, which fixes vulnerability CVE-2018-1000873. (#23) 103 | 104 | - Bumped Java version to 1.8. This was necessary due to Jackson upgrade. (#23) 105 | 106 | - Added `marker:name` resolver. (#21) 107 | -------------------------------------------------------------------------------- /layout-benchmark/benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # Copyright 2017-2020 Volkan Yazıcı 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permits and 16 | # limitations under the License. 17 | 18 | 19 | import json 20 | import logging 21 | import os 22 | import re 23 | import subprocess 24 | import time 25 | 26 | 27 | logging.basicConfig( 28 | level=logging.INFO, 29 | format="%(asctime)s %(levelname)7s [%(name)s] %(message)s", 30 | datefmt="%Y-%m-%d %H:%M:%S") 31 | LOGGER = logging.getLogger(__name__) 32 | 33 | 34 | BENCHMARK_DIR = os.path.dirname(os.path.realpath(__file__)) 35 | PROJECT_DIR = os.path.abspath(os.path.join(BENCHMARK_DIR, "..")) 36 | 37 | 38 | def get_json_output_file(): 39 | return os.path.join(BENCHMARK_DIR, "results.json") 40 | 41 | 42 | def get_mvn_output_file(): 43 | return os.path.join(BENCHMARK_DIR, "results.out") 44 | 45 | 46 | def run_benchmark(): 47 | LOGGER.info("Starting benchmark...") 48 | start_instant_seconds = time.time() 49 | json_output_file = get_json_output_file() 50 | mvn_output_file = get_mvn_output_file() 51 | with open(mvn_output_file, "w") as mvn_output_file_handle: 52 | env = os.environ.copy() 53 | env["MAVEN_OPTS"] = "-XX:+TieredCompilation" 54 | popen = subprocess.Popen( 55 | ["taskset", 56 | "-c", "0", 57 | "time", 58 | "mvn", 59 | "-pl", "layout", 60 | "exec:java", 61 | "-Dlog4j2.garbagefreeThreadContextMap=true", 62 | "-Dlog4j2.enableDirectEncoders=true", 63 | "-Dlog4j2.enable.threadlocals=true", 64 | "-Dlog4j2.is.webapp=false", 65 | "-Dlog4j2.logstashLayoutBenchmark.jsonOutputFile={}".format(json_output_file)], 66 | env=env, 67 | bufsize=1, 68 | universal_newlines=True, 69 | cwd=PROJECT_DIR, 70 | stderr=subprocess.PIPE, 71 | stdout=mvn_output_file_handle) 72 | popen.communicate() 73 | return_code = popen.returncode 74 | if return_code != 0: 75 | raise Exception("benchmark failure (return_code={})".format(return_code)) 76 | stop_instant_seconds = time.time() 77 | total_duration_seconds = stop_instant_seconds - start_instant_seconds 78 | LOGGER.info("Completed benchmark... (total_duration_seconds={})".format(total_duration_seconds)) 79 | 80 | 81 | def read_results(): 82 | 83 | # Collect results. 84 | results = [] 85 | json_output_file = get_json_output_file() 86 | with open(json_output_file) as json_output_file_handle: 87 | json_dicts = json.load(json_output_file_handle) 88 | for json_dict in json_dicts: 89 | results.append({ 90 | "benchmark": json_dict["benchmark"], 91 | "op_rate": json_dict["primaryMetric"]["scorePercentiles"]["99.0"], 92 | "gc_rate": json_dict["secondaryMetrics"][u"·gc.alloc.rate.norm"]["scorePercentiles"]["99.0"] 93 | }) 94 | 95 | # Enrich results with normalized op rate slowdown. 96 | max_op_rate = max([result["op_rate"] for result in results]) 97 | for result in results: 98 | result["op_rate_norm"] = result["op_rate"] / max_op_rate 99 | 100 | # Sort and return results. 101 | results.sort(key=lambda result: result["op_rate"], reverse=True) 102 | return results 103 | 104 | 105 | def plot_results(): 106 | results = read_results() 107 | html_file = os.path.join(BENCHMARK_DIR, "results.html") 108 | with open(html_file, "w") as html_file_handle: 109 | html_file_handle.write(""" 110 | 111 | 112 | 113 | 114 | log4j2-logstash-layout Benchmark Results 115 | 116 | 117 | 128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | """) 138 | for result in results: 139 | benchmark_name = re.sub(r".*\.([a-zA-Z0-9]+)", r"\1", result["benchmark"]) 140 | html_file_handle.write(""" 141 | 142 | 143 | 144 | 145 | 146 | """.format( 147 | benchmark_name, 148 | benchmark_name, 149 | "{:,.0f}".format(result["op_rate"] * 1e3), 150 | ("▉" * (1 + int(19 * result["op_rate_norm"]))) + (" ({:.0f}%)".format(100 * result["op_rate_norm"])), 151 | "{:,.1f}".format(max(0, result["gc_rate"])))) 152 | html_file_handle.write(""" 153 | 154 |
Benchmarkops/sec*B/op*
{}{}{}{}
155 |

156 | * 99th percentile 157 |

158 |
159 | 160 | """) 161 | 162 | 163 | def main(): 164 | run_benchmark() 165 | plot_results() 166 | 167 | 168 | if __name__ == "__main__": 169 | main() 170 | -------------------------------------------------------------------------------- /layout-benchmark/maven-version-rules.xml: -------------------------------------------------------------------------------- 1 | ../maven-version-rules.xml -------------------------------------------------------------------------------- /layout-benchmark/results.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | log4j2-logstash-layout Benchmark Results 7 | 8 | 9 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
Benchmarkops/sec*B/op*
liteLogstashLayout4GelfLayout1,517,062▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ (100%)0.0
liteLogstashLayout4EcsLayout1,196,255▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ (79%)0.0
liteGelfLayout1,184,922▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ (78%)0.0
liteLogstashLayout4JsonLayout870,012▉▉▉▉▉▉▉▉▉▉▉ (57%)0.0
liteEcsLayout836,648▉▉▉▉▉▉▉▉▉▉▉ (55%)0.0
liteDefaultJsonLayout506,985▉▉▉▉▉▉▉ (33%)5,331,680.0
liteCustomJsonLayout446,243▉▉▉▉▉▉ (29%)5,740,400.0
fullLogstashLayout4JsonLayout118,294▉▉ (8%)104,000.1
fullLogstashLayout4GelfLayout73,102▉ (5%)35,663,200.3
fullLogstashLayout4EcsLayout60,569▉ (4%)35,631,200.4
fullEcsLayout27,887▉ (2%)46,479,200.5
fullGelfLayout21,458▉ (1%)58,911,200.7
fullDefaultJsonLayout13,513▉ (1%)234,102,401.5
fullCustomJsonLayout13,511▉ (1%)234,238,401.5
116 |

117 | * 99th percentile 118 |

119 |
120 | 121 | -------------------------------------------------------------------------------- /layout-demo/maven-version-rules.xml: -------------------------------------------------------------------------------- 1 | ../maven-version-rules.xml -------------------------------------------------------------------------------- /layout-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | log4j2-logstash-layout-parent 8 | com.vlkan.log4j2 9 | 1.0.6-SNAPSHOT 10 | 11 | 12 | log4j2-logstash-layout-demo 13 | 14 | 15 | 16 | 17 | org.apache.logging.log4j 18 | log4j-core 19 | 20 | 21 | 22 | com.vlkan.log4j2 23 | log4j2-logstash-layout 24 | ${project.version} 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /layout-demo/src/main/java/com/vlkan/log4j2/logstash/layout/demo/LogstashLayoutDemo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.demo; 18 | 19 | import org.apache.logging.log4j.LogManager; 20 | import org.apache.logging.log4j.Logger; 21 | 22 | public enum LogstashLayoutDemo {; 23 | 24 | public static void main(String[] args) { 25 | Logger logger = LogManager.getLogger(LogstashLayoutDemo.class); 26 | logger.info("Hello, world!"); 27 | RuntimeException error = new RuntimeException("test"); 28 | logger.error("Hello, error!", error); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /layout-demo/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /layout-fatjar/maven-version-rules.xml: -------------------------------------------------------------------------------- 1 | ../maven-version-rules.xml -------------------------------------------------------------------------------- /layout-fatjar/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.vlkan.log4j2 8 | log4j2-logstash-layout-parent 9 | 1.0.6-SNAPSHOT 10 | 11 | 12 | log4j2-logstash-layout-fatjar 13 | 14 | 15 | 16 | 17 | com.vlkan.log4j2 18 | log4j2-logstash-layout 19 | ${project.version} 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-shade-plugin 31 | 32 | 33 | package 34 | 35 | shade 36 | 37 | 38 | 39 | 40 | false 41 | 42 | 43 | com.fasterxml.jackson 44 | ${project.groupId}.logstash.layout.jackson 45 | 46 | 47 | org.apache.commons.lang3 48 | ${project.groupId}.logstash.layout.lang3 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /layout-fatjar/src/main/java/com/vlkan/log4j2/logstash/layout/LogstashLayoutFatJar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | /** 20 | * Dummy enum to generate source and javadoc JARs required by Maven Central. 21 | */ 22 | public enum LogstashLayoutFatJar { 23 | 24 | // Has nothing. 25 | 26 | } 27 | -------------------------------------------------------------------------------- /layout/maven-version-rules.xml: -------------------------------------------------------------------------------- 1 | ../maven-version-rules.xml -------------------------------------------------------------------------------- /layout/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.vlkan.log4j2 8 | log4j2-logstash-layout-parent 9 | 1.0.6-SNAPSHOT 10 | 11 | 12 | log4j2-logstash-layout 13 | 14 | 15 | 16 | 17 | org.apache.logging.log4j 18 | log4j-core 19 | provided 20 | 21 | 22 | 23 | com.fasterxml.jackson.core 24 | jackson-databind 25 | 26 | 27 | 28 | org.apache.commons 29 | commons-lang3 30 | 31 | 32 | 33 | junit 34 | junit 35 | test 36 | 37 | 38 | 39 | org.assertj 40 | assertj-core 41 | test 42 | 43 | 44 | 45 | org.mockito 46 | mockito-core 47 | test 48 | 49 | 50 | 51 | org.openjdk.jmh 52 | jmh-core 53 | test 54 | 55 | 56 | 57 | org.openjdk.jmh 58 | jmh-generator-annprocess 59 | test 60 | 61 | 62 | 63 | co.elastic.logging 64 | log4j2-ecs-layout 65 | test 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-surefire-plugin 76 | ${maven-surefire-plugin.version} 77 | 78 | true 79 | 82 | -Dfile.encoding=US-ASCII 83 | 84 | 85 | 86 | 87 | tla-enabled 88 | 89 | test 90 | 91 | 92 | ${maven.test.skip} 93 | 94 | true 95 | false 96 | 97 | 98 | 99 | 100 | 101 | tla-disabled 102 | 103 | test 104 | 105 | 106 | ${maven.test.skip} 107 | 108 | false 109 | true 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.codehaus.mojo 118 | build-helper-maven-plugin 119 | 120 | 121 | add-perf-source 122 | generate-test-sources 123 | 124 | add-test-source 125 | 126 | 127 | 128 | src/test/perf 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.codehaus.mojo 137 | exec-maven-plugin 138 | 139 | 140 | 141 | java 142 | 143 | 144 | 145 | 146 | test 147 | com.vlkan.log4j2.logstash.layout.LogstashLayoutBenchmark 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/LogstashLayoutSerializationContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import com.vlkan.log4j2.logstash.layout.util.ByteBufferOutputStream; 21 | 22 | interface LogstashLayoutSerializationContext extends AutoCloseable { 23 | 24 | ByteBufferOutputStream getOutputStream(); 25 | 26 | JsonGenerator getJsonGenerator(); 27 | 28 | void reset(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/LogstashLayoutSerializationContexts.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | import com.fasterxml.jackson.core.JsonFactory; 20 | import com.fasterxml.jackson.core.JsonGenerator; 21 | import com.fasterxml.jackson.core.PrettyPrinter; 22 | import com.fasterxml.jackson.core.filter.FilteringGeneratorDelegate; 23 | import com.fasterxml.jackson.core.filter.TokenFilter; 24 | import com.fasterxml.jackson.core.io.SerializedString; 25 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; 26 | import com.fasterxml.jackson.core.util.JsonGeneratorDelegate; 27 | import com.fasterxml.jackson.databind.ObjectMapper; 28 | import com.vlkan.log4j2.logstash.layout.util.ByteBufferOutputStream; 29 | 30 | import java.io.IOException; 31 | import java.io.OutputStream; 32 | import java.io.StringReader; 33 | import java.nio.Buffer; 34 | import java.nio.ByteBuffer; 35 | import java.util.function.Supplier; 36 | 37 | enum LogstashLayoutSerializationContexts {; 38 | 39 | private static final SerializedString EMPTY_SERIALIZED_STRING = new SerializedString(""); 40 | 41 | private static final PrettyPrinter PRETTY_PRINTER = new DefaultPrettyPrinter(""); 42 | 43 | static Supplier createSupplier( 44 | ObjectMapper objectMapper, 45 | int maxByteCount, 46 | boolean prettyPrintEnabled, 47 | boolean emptyPropertyExclusionEnabled, 48 | int maxStringLength) { 49 | JsonFactory jsonFactory = new JsonFactory(objectMapper); 50 | return () -> { 51 | ByteBufferOutputStream outputStream = new ByteBufferOutputStream(maxByteCount); 52 | JsonGenerator jsonGenerator = createJsonGenerator( 53 | jsonFactory, 54 | outputStream, 55 | prettyPrintEnabled, 56 | emptyPropertyExclusionEnabled, 57 | maxStringLength); 58 | return new LogstashLayoutSerializationContext() { 59 | 60 | @Override 61 | public ByteBufferOutputStream getOutputStream() { 62 | return outputStream; 63 | } 64 | 65 | @Override 66 | public JsonGenerator getJsonGenerator() { 67 | return jsonGenerator; 68 | } 69 | 70 | @Override 71 | public void close() throws Exception { 72 | jsonGenerator.close(); 73 | } 74 | 75 | @Override 76 | public void reset() { 77 | ByteBuffer byteBuffer = outputStream.getByteBuffer(); 78 | // noinspection RedundantCast (for Java 8 compatibility) 79 | ((Buffer) byteBuffer).clear(); 80 | } 81 | 82 | }; 83 | }; 84 | } 85 | 86 | private static JsonGenerator createJsonGenerator( 87 | JsonFactory jsonFactory, 88 | OutputStream outputStream, 89 | boolean prettyPrintEnabled, 90 | boolean emptyPropertyExclusionEnabled, 91 | int maxStringLength) { 92 | try { 93 | JsonGenerator jsonGenerator = jsonFactory.createGenerator(outputStream); 94 | jsonGenerator.setRootValueSeparator(EMPTY_SERIALIZED_STRING); 95 | if (prettyPrintEnabled) { 96 | jsonGenerator.setPrettyPrinter(PRETTY_PRINTER); 97 | } 98 | if (maxStringLength > 0) { 99 | jsonGenerator = new StringTruncatingGeneratorDelegate(jsonGenerator, maxStringLength); 100 | } 101 | // TokenFilter has to come last due to FasterXML/jackson-core#609. 102 | if (emptyPropertyExclusionEnabled) { 103 | jsonGenerator = new FilteringGeneratorDelegate(jsonGenerator, NullExcludingTokenFilter.INSTANCE, true, true); 104 | } 105 | return jsonGenerator; 106 | } catch (IOException error) { 107 | throw new RuntimeException("failed creating JsonGenerator", error); 108 | } 109 | } 110 | 111 | private static class NullExcludingTokenFilter extends TokenFilter { 112 | 113 | private static final NullExcludingTokenFilter INSTANCE = new NullExcludingTokenFilter(); 114 | 115 | @Override 116 | public boolean includeNull() { 117 | return false; 118 | } 119 | 120 | } 121 | 122 | private static class StringTruncatingGeneratorDelegate extends JsonGeneratorDelegate { 123 | 124 | private final int maxStringLength; 125 | 126 | private StringTruncatingGeneratorDelegate(JsonGenerator jsonGenerator, int maxStringLength) { 127 | super(jsonGenerator); 128 | this.maxStringLength = maxStringLength; 129 | } 130 | 131 | @Override 132 | public void writeString(String text) throws IOException { 133 | if (text == null) { 134 | writeNull(); 135 | } else if (maxStringLength <= 0 || maxStringLength >= text.length()) { 136 | super.writeString(text); 137 | } else { 138 | StringReader textReader = new StringReader(text); 139 | super.writeString(textReader, maxStringLength); 140 | } 141 | } 142 | 143 | @Override 144 | public void writeFieldName(String name) throws IOException { 145 | if (maxStringLength <= 0 || maxStringLength >= name.length()) { 146 | super.writeFieldName(name); 147 | } else { 148 | String truncatedName = name.substring(0, maxStringLength); 149 | super.writeFieldName(truncatedName); 150 | } 151 | } 152 | 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ContextDataResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import com.vlkan.log4j2.logstash.layout.util.JsonGenerators; 21 | import org.apache.logging.log4j.core.LogEvent; 22 | import org.apache.logging.log4j.util.IndexedStringMap; 23 | import org.apache.logging.log4j.util.ReadOnlyStringMap; 24 | 25 | import java.io.IOException; 26 | import java.util.regex.Pattern; 27 | 28 | /** 29 | * Add Mapped Diagnostic Context (MDC). 30 | */ 31 | class ContextDataResolver implements EventResolver { 32 | 33 | private final EventResolverContext context; 34 | 35 | private final String key; 36 | 37 | ContextDataResolver(EventResolverContext context, String key) { 38 | this.context = context; 39 | this.key = key; 40 | } 41 | 42 | static String getName() { 43 | return "mdc"; 44 | } 45 | 46 | @Override 47 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 48 | 49 | // Retrieve context data. 50 | ReadOnlyStringMap contextData = logEvent.getContextData(); 51 | if (contextData == null || contextData.isEmpty()) { 52 | jsonGenerator.writeNull(); 53 | return; 54 | } 55 | 56 | // Check if key matches. 57 | if (key != null) { 58 | Object value = contextData.getValue(key); 59 | boolean valueExcluded = isValueExcluded(context, value); 60 | if (valueExcluded) { 61 | jsonGenerator.writeNull(); 62 | } else { 63 | JsonGenerators.writeObject(jsonGenerator, value); 64 | } 65 | return; 66 | } 67 | 68 | // Otherwise return all context data matching the MDC key pattern. 69 | Pattern keyPattern = context.getMdcKeyPattern(); 70 | jsonGenerator.writeStartObject(); 71 | if (contextData instanceof IndexedStringMap) { // First, try access-by-id, which is GC free. 72 | resolveIndexedMap(jsonGenerator, (IndexedStringMap) contextData, keyPattern); 73 | } else { // Otherwise, fallback to ReadOnlyStringMap#forEach(). 74 | resolveGenericMap(jsonGenerator, contextData, keyPattern); 75 | } 76 | jsonGenerator.writeEndObject(); 77 | 78 | } 79 | 80 | private void resolveIndexedMap(JsonGenerator jsonGenerator, IndexedStringMap contextData, Pattern keyPattern) { 81 | for (int entryIndex = 0; entryIndex < contextData.size(); entryIndex++) { 82 | String key = contextData.getKeyAt(entryIndex); 83 | Object value = contextData.getValueAt(entryIndex); 84 | boolean keyMatches = keyPattern == null || keyPattern.matcher(key).matches(); 85 | resolveEntry(jsonGenerator, key, value, keyMatches); 86 | } 87 | } 88 | 89 | private void resolveGenericMap(JsonGenerator jsonGenerator, ReadOnlyStringMap contextData, Pattern keyPattern) { 90 | contextData.forEach((key, value) -> { 91 | boolean keyMatches = keyPattern == null || keyPattern.matcher(key).matches(); 92 | resolveEntry(jsonGenerator, key, value, keyMatches); 93 | }); 94 | } 95 | 96 | private void resolveEntry(JsonGenerator jsonGenerator, String key, Object value, boolean keyMatches) { 97 | if (keyMatches) { 98 | boolean valueExcluded = isValueExcluded(context, value); 99 | if (!valueExcluded) { 100 | try { 101 | jsonGenerator.writeFieldName(key); 102 | JsonGenerators.writeObject(jsonGenerator, value); 103 | } catch (IOException error) { 104 | String message = String.format("failed to append MDC field (key=%s, value=%s)", key, value); 105 | throw new RuntimeException(message, error); 106 | } 107 | } 108 | } 109 | } 110 | 111 | private static boolean isValueExcluded(EventResolverContext context, Object value) { 112 | return context.isEmptyPropertyExclusionEnabled() && 113 | (value == null || (value instanceof String && ((String) value).isEmpty())); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ContextDataResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class ContextDataResolverFactory implements EventResolverFactory { 20 | 21 | private static final ContextDataResolverFactory INSTANCE = new ContextDataResolverFactory(); 22 | 23 | private ContextDataResolverFactory() {} 24 | 25 | static ContextDataResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return ContextDataResolver.getName(); 32 | } 33 | 34 | @Override 35 | public ContextDataResolver create(EventResolverContext context, String key) { 36 | return new ContextDataResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ContextStackResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.logging.log4j.ThreadContext; 21 | import org.apache.logging.log4j.core.LogEvent; 22 | 23 | import java.io.IOException; 24 | import java.util.regex.Pattern; 25 | 26 | /** 27 | * Add Nested Diagnostic Context (NDC). 28 | */ 29 | class ContextStackResolver implements EventResolver { 30 | 31 | private final EventResolverContext context; 32 | 33 | ContextStackResolver(EventResolverContext context) { 34 | this.context = context; 35 | } 36 | 37 | static String getName() { 38 | return "ndc"; 39 | } 40 | 41 | @Override 42 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 43 | ThreadContext.ContextStack contextStack = logEvent.getContextStack(); 44 | if (contextStack.getDepth() == 0) { 45 | jsonGenerator.writeNull(); 46 | return; 47 | } 48 | Pattern itemPattern = context.getNdcPattern(); 49 | boolean arrayStarted = false; 50 | for (String contextStackItem : contextStack.asList()) { 51 | boolean matches = itemPattern == null || itemPattern.matcher(contextStackItem).matches(); 52 | if (matches) { 53 | if (!arrayStarted) { 54 | jsonGenerator.writeStartArray(); 55 | arrayStarted = true; 56 | } 57 | jsonGenerator.writeString(contextStackItem); 58 | } 59 | } 60 | if (arrayStarted) { 61 | jsonGenerator.writeEndArray(); 62 | } else { 63 | jsonGenerator.writeNull(); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ContextStackResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class ContextStackResolverFactory implements EventResolverFactory { 20 | 21 | private static final ContextStackResolverFactory INSTANCE = new ContextStackResolverFactory(); 22 | 23 | private ContextStackResolverFactory() {} 24 | 25 | static ContextStackResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return ContextStackResolver.getName(); 32 | } 33 | 34 | @Override 35 | public ContextStackResolver create(EventResolverContext context, String key) { 36 | return new ContextStackResolver(context); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/EndOfBatchResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.logging.log4j.core.LogEvent; 21 | 22 | import java.io.IOException; 23 | 24 | class EndOfBatchResolver implements EventResolver { 25 | 26 | private static final EndOfBatchResolver INSTANCE = new EndOfBatchResolver(); 27 | 28 | private EndOfBatchResolver() {} 29 | 30 | static EndOfBatchResolver getInstance() { 31 | return INSTANCE; 32 | } 33 | 34 | static String getName() { 35 | return "endOfBatch"; 36 | } 37 | 38 | @Override 39 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 40 | boolean endOfBatch = logEvent.isEndOfBatch(); 41 | jsonGenerator.writeBoolean(endOfBatch); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/EndOfBatchResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class EndOfBatchResolverFactory implements EventResolverFactory { 20 | 21 | private static final EndOfBatchResolverFactory INSTANCE = new EndOfBatchResolverFactory(); 22 | 23 | private EndOfBatchResolverFactory() {} 24 | 25 | static EndOfBatchResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return EndOfBatchResolver.getName(); 32 | } 33 | 34 | @Override 35 | public EndOfBatchResolver create(EventResolverContext context, String key) { 36 | return EndOfBatchResolver.getInstance(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/EventResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import org.apache.logging.log4j.core.LogEvent; 20 | 21 | interface EventResolver extends TemplateResolver {} 22 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/EventResolverFactories.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import org.apache.logging.log4j.core.LogEvent; 20 | 21 | import java.util.*; 22 | 23 | enum EventResolverFactories {; 24 | 25 | private static final Map>> RESOLVER_FACTORY_BY_NAME = 26 | createResolverFactoryByName(); 27 | 28 | private static Map>> createResolverFactoryByName() { 29 | 30 | // Collect resolver factories. 31 | List> resolverFactories = Arrays.asList( 32 | ContextDataResolverFactory.getInstance(), 33 | ContextStackResolverFactory.getInstance(), 34 | EndOfBatchResolverFactory.getInstance(), 35 | ExceptionResolverFactory.getInstance(), 36 | ExceptionRootCauseResolverFactory.getInstance(), 37 | LevelResolverFactory.getInstance(), 38 | LoggerResolverFactory.getInstance(), 39 | MainMapResolverFactory.getInstance(), 40 | MapResolverFactory.getInstance(), 41 | MarkerResolverFactory.getInstance(), 42 | MessageResolverFactory.getInstance(), 43 | SourceResolverFactory.getInstance(), 44 | ThreadResolverFactory.getInstance(), 45 | TimestampResolverFactory.getInstance()); 46 | 47 | // Convert collection to map. 48 | Map>> resolverFactoryByName = new LinkedHashMap<>(); 49 | for (EventResolverFactory resolverFactory : resolverFactories) { 50 | resolverFactoryByName.put(resolverFactory.getName(), resolverFactory); 51 | } 52 | return Collections.unmodifiableMap(resolverFactoryByName); 53 | 54 | } 55 | 56 | static Map>> getResolverFactoryByName() { 57 | return RESOLVER_FACTORY_BY_NAME; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/EventResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import org.apache.logging.log4j.core.LogEvent; 20 | 21 | interface EventResolverFactory> extends TemplateResolverFactory {} 22 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ExceptionInternalResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | abstract class ExceptionInternalResolverFactory { 20 | 21 | private static final EventResolver NULL_RESOLVER = (ignored, jsonGenerator) -> jsonGenerator.writeNull(); 22 | 23 | EventResolver createInternalResolver(EventResolverContext context, String key) { 24 | 25 | // Split the key into its major and minor components. 26 | String majorKey; 27 | String minorKey; 28 | int colonIndex = key.indexOf(':'); 29 | if (colonIndex >= 0) { 30 | majorKey = key.substring(0, colonIndex); 31 | minorKey = key.substring(colonIndex + 1); 32 | } else { 33 | majorKey = key; 34 | minorKey = ""; 35 | } 36 | 37 | // Create the resolver. 38 | switch (majorKey) { 39 | case "className": return createClassNameResolver(); 40 | case "message": return createMessageResolver(context); 41 | case "stackTrace": return createStackTraceResolver(context, minorKey); 42 | } 43 | throw new IllegalArgumentException("unknown key: " + key); 44 | 45 | } 46 | 47 | abstract EventResolver createClassNameResolver(); 48 | 49 | abstract EventResolver createMessageResolver(EventResolverContext context); 50 | 51 | private EventResolver createStackTraceResolver(EventResolverContext context, String minorKey) { 52 | if (!context.isStackTraceEnabled()) { 53 | return NULL_RESOLVER; 54 | } 55 | switch (minorKey) { 56 | case "text": return createStackTraceTextResolver(context); 57 | case "": return createStackTraceObjectResolver(context); 58 | } 59 | throw new IllegalArgumentException("unknown minor key: " + minorKey); 60 | } 61 | 62 | abstract EventResolver createStackTraceTextResolver(EventResolverContext context); 63 | 64 | abstract EventResolver createStackTraceObjectResolver(EventResolverContext context); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ExceptionResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.commons.lang3.StringUtils; 21 | import org.apache.logging.log4j.core.LogEvent; 22 | 23 | import java.io.IOException; 24 | 25 | class ExceptionResolver implements EventResolver { 26 | 27 | private static final ExceptionInternalResolverFactory INTERNAL_RESOLVER_FACTORY = 28 | new ExceptionInternalResolverFactory() { 29 | 30 | @Override 31 | EventResolver createClassNameResolver() { 32 | return (logEvent, jsonGenerator) -> { 33 | Throwable exception = logEvent.getThrown(); 34 | if (exception == null) { 35 | jsonGenerator.writeNull(); 36 | } else { 37 | String exceptionClassName = exception.getClass().getCanonicalName(); 38 | jsonGenerator.writeString(exceptionClassName); 39 | } 40 | }; 41 | } 42 | 43 | @Override 44 | EventResolver createMessageResolver(EventResolverContext context) { 45 | return (logEvent, jsonGenerator) -> { 46 | Throwable exception = logEvent.getThrown(); 47 | if (exception != null) { 48 | String exceptionMessage = exception.getMessage(); 49 | boolean exceptionMessageExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(exceptionMessage); 50 | if (!exceptionMessageExcluded) { 51 | jsonGenerator.writeString(exceptionMessage); 52 | return; 53 | } 54 | } 55 | jsonGenerator.writeNull(); 56 | }; 57 | } 58 | 59 | @Override 60 | EventResolver createStackTraceTextResolver(EventResolverContext context) { 61 | StackTraceTextResolver stackTraceTextResolver = new StackTraceTextResolver(context.getWriterCapacity()); 62 | return (logEvent, jsonGenerator) -> { 63 | Throwable exception = logEvent.getThrown(); 64 | if (exception == null) { 65 | jsonGenerator.writeNull(); 66 | } else { 67 | stackTraceTextResolver.resolve(exception, jsonGenerator); 68 | } 69 | }; 70 | } 71 | 72 | @Override 73 | EventResolver createStackTraceObjectResolver(EventResolverContext context) { 74 | return (logEvent, jsonGenerator) -> { 75 | Throwable exception = logEvent.getThrown(); 76 | if (exception == null) { 77 | jsonGenerator.writeNull(); 78 | } else { 79 | context.getStackTraceObjectResolver().resolve(exception, jsonGenerator); 80 | } 81 | }; 82 | } 83 | 84 | }; 85 | 86 | private final EventResolver internalResolver; 87 | 88 | ExceptionResolver(EventResolverContext context, String key) { 89 | this.internalResolver = INTERNAL_RESOLVER_FACTORY.createInternalResolver(context, key); 90 | } 91 | 92 | static String getName() { 93 | return "exception"; 94 | } 95 | 96 | @Override 97 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 98 | internalResolver.resolve(logEvent, jsonGenerator); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ExceptionResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class ExceptionResolverFactory implements EventResolverFactory { 20 | 21 | private static final ExceptionResolverFactory INSTANCE = new ExceptionResolverFactory(); 22 | 23 | private ExceptionResolverFactory() {} 24 | 25 | static ExceptionResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return ExceptionResolver.getName(); 32 | } 33 | 34 | @Override 35 | public ExceptionResolver create(EventResolverContext context, String key) { 36 | return new ExceptionResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ExceptionRootCauseResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import com.vlkan.log4j2.logstash.layout.util.Throwables; 21 | import org.apache.commons.lang3.StringUtils; 22 | import org.apache.logging.log4j.core.LogEvent; 23 | 24 | import java.io.IOException; 25 | 26 | class ExceptionRootCauseResolver implements EventResolver { 27 | 28 | private static final ExceptionInternalResolverFactory INTERNAL_RESOLVER_FACTORY = 29 | new ExceptionInternalResolverFactory() { 30 | 31 | @Override 32 | EventResolver createClassNameResolver() { 33 | return (logEvent, jsonGenerator) -> { 34 | Throwable exception = logEvent.getThrown(); 35 | if (exception == null) { 36 | jsonGenerator.writeNull(); 37 | } else { 38 | Throwable rootCause = Throwables.getRootCause(exception); 39 | String rootCauseClassName = rootCause.getClass().getCanonicalName(); 40 | jsonGenerator.writeString(rootCauseClassName); 41 | } 42 | }; 43 | } 44 | 45 | @Override 46 | EventResolver createMessageResolver(EventResolverContext context) { 47 | return (logEvent, jsonGenerator) -> { 48 | Throwable exception = logEvent.getThrown(); 49 | if (exception != null) { 50 | Throwable rootCause = Throwables.getRootCause(exception); 51 | String rootCauseMessage = rootCause.getMessage(); 52 | boolean rootCauseMessageExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(rootCauseMessage); 53 | if (!rootCauseMessageExcluded) { 54 | jsonGenerator.writeString(rootCauseMessage); 55 | return; 56 | } 57 | } 58 | jsonGenerator.writeNull(); 59 | }; 60 | } 61 | 62 | @Override 63 | EventResolver createStackTraceTextResolver(EventResolverContext context) { 64 | StackTraceTextResolver stackTraceTextResolver = new StackTraceTextResolver(context.getWriterCapacity()); 65 | return (logEvent, jsonGenerator) -> { 66 | Throwable exception = logEvent.getThrown(); 67 | if (exception == null) { 68 | jsonGenerator.writeNull(); 69 | } else { 70 | Throwable rootCause = Throwables.getRootCause(exception); 71 | stackTraceTextResolver.resolve(rootCause, jsonGenerator); 72 | } 73 | }; 74 | } 75 | 76 | @Override 77 | EventResolver createStackTraceObjectResolver(EventResolverContext context) { 78 | return (logEvent, jsonGenerator) -> { 79 | Throwable exception = logEvent.getThrown(); 80 | if (exception == null) { 81 | jsonGenerator.writeNull(); 82 | } else { 83 | Throwable rootCause = Throwables.getRootCause(exception); 84 | context.getStackTraceObjectResolver().resolve(rootCause, jsonGenerator); 85 | } 86 | }; 87 | } 88 | 89 | }; 90 | 91 | private final EventResolver internalResolver; 92 | 93 | ExceptionRootCauseResolver(EventResolverContext context, String key) { 94 | this.internalResolver = INTERNAL_RESOLVER_FACTORY.createInternalResolver(context, key); 95 | } 96 | 97 | static String getName() { 98 | return "exceptionRootCause"; 99 | } 100 | 101 | @Override 102 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 103 | internalResolver.resolve(logEvent, jsonGenerator); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ExceptionRootCauseResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class ExceptionRootCauseResolverFactory implements EventResolverFactory { 20 | 21 | private static final ExceptionRootCauseResolverFactory INSTANCE = new ExceptionRootCauseResolverFactory(); 22 | 23 | private ExceptionRootCauseResolverFactory() {} 24 | 25 | static ExceptionRootCauseResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return ExceptionRootCauseResolver.getName(); 32 | } 33 | 34 | @Override 35 | public ExceptionRootCauseResolver create(EventResolverContext context, String key) { 36 | return new ExceptionRootCauseResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/LevelResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.logging.log4j.core.LogEvent; 21 | import org.apache.logging.log4j.core.net.Severity; 22 | 23 | import java.io.IOException; 24 | 25 | class LevelResolver implements EventResolver { 26 | 27 | private static final EventResolver NAME_RESOLVER = (logEvent, jsonGenerator) -> { 28 | String levelName = logEvent.getLevel().name(); 29 | jsonGenerator.writeString(levelName); 30 | }; 31 | 32 | private static final EventResolver SEVERITY_NAME_RESOLVER = (logEvent, jsonGenerator) -> { 33 | String severityName = Severity.getSeverity(logEvent.getLevel()).name(); 34 | jsonGenerator.writeString(severityName); 35 | }; 36 | 37 | private static final EventResolver SEVERITY_CODE_RESOLVER = (logEvent, jsonGenerator) -> { 38 | int severityCode = Severity.getSeverity(logEvent.getLevel()).getCode(); 39 | jsonGenerator.writeNumber(severityCode); 40 | }; 41 | 42 | private final EventResolver internalResolver; 43 | 44 | LevelResolver(String key) { 45 | if (key == null) { 46 | internalResolver = NAME_RESOLVER; 47 | } else if ("severity".equals(key)) { 48 | internalResolver = SEVERITY_NAME_RESOLVER; 49 | } else if ("severity:code".equals(key)) { 50 | internalResolver = SEVERITY_CODE_RESOLVER; 51 | } else { 52 | throw new IllegalArgumentException("unknown key: " + key); 53 | } 54 | } 55 | 56 | static String getName() { 57 | return "level"; 58 | } 59 | 60 | @Override 61 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 62 | internalResolver.resolve(logEvent, jsonGenerator); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/LevelResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class LevelResolverFactory implements EventResolverFactory { 20 | 21 | private static final LevelResolverFactory INSTANCE = new LevelResolverFactory(); 22 | 23 | private LevelResolverFactory() {} 24 | 25 | static LevelResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return LevelResolver.getName(); 32 | } 33 | 34 | @Override 35 | public LevelResolver create(EventResolverContext context, String key) { 36 | return new LevelResolver(key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/LoggerResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.commons.lang3.StringUtils; 21 | import org.apache.logging.log4j.core.LogEvent; 22 | 23 | import java.io.IOException; 24 | 25 | class LoggerResolver implements EventResolver { 26 | 27 | private final EventResolver internalResolver; 28 | 29 | LoggerResolver(EventResolverContext context, String key) { 30 | this.internalResolver = createInternalResolver(context, key); 31 | } 32 | 33 | private static EventResolver createInternalResolver(EventResolverContext context, String key) { 34 | switch (key) { 35 | case "name": return createNameResolver(context); 36 | case "fqcn": return createFqcnResolver(context); 37 | } 38 | throw new IllegalArgumentException("unknown key: " + key); 39 | } 40 | 41 | private static EventResolver createNameResolver(EventResolverContext context) { 42 | return (logEvent, jsonGenerator) -> { 43 | String loggerName = logEvent.getLoggerName(); 44 | writeText(jsonGenerator, context, loggerName); 45 | }; 46 | } 47 | 48 | private static EventResolver createFqcnResolver(EventResolverContext context) { 49 | return (logEvent, jsonGenerator) -> { 50 | String loggerFqcn = logEvent.getLoggerFqcn(); 51 | writeText(jsonGenerator, context, loggerFqcn); 52 | }; 53 | } 54 | 55 | private static void writeText(JsonGenerator jsonGenerator, EventResolverContext context, String text) throws IOException { 56 | boolean textExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(text); 57 | if (textExcluded) { 58 | jsonGenerator.writeNull(); 59 | } else { 60 | jsonGenerator.writeString(text); 61 | } 62 | } 63 | 64 | static String getName() { 65 | return "logger"; 66 | } 67 | 68 | @Override 69 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 70 | internalResolver.resolve(logEvent, jsonGenerator); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/LoggerResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class LoggerResolverFactory implements EventResolverFactory { 20 | 21 | private static final LoggerResolverFactory INSTANCE = new LoggerResolverFactory(); 22 | 23 | private LoggerResolverFactory() {} 24 | 25 | static LoggerResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return LoggerResolver.getName(); 32 | } 33 | 34 | @Override 35 | public LoggerResolver create(EventResolverContext context, String key) { 36 | return new LoggerResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/MainMapResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | 21 | import java.io.IOException; 22 | 23 | import org.apache.commons.lang3.StringUtils; 24 | import org.apache.logging.log4j.core.LogEvent; 25 | import org.apache.logging.log4j.core.lookup.MainMapLookup; 26 | 27 | public class MainMapResolver implements EventResolver { 28 | 29 | private static final MainMapLookup MAIN_MAP_LOOKUP = new MainMapLookup(); 30 | 31 | private final EventResolverContext context; 32 | 33 | private final String key; 34 | 35 | static String getName() { 36 | return "main"; 37 | } 38 | 39 | MainMapResolver(EventResolverContext context, String key) { 40 | this.context = context; 41 | this.key = key; 42 | } 43 | 44 | @Override 45 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 46 | String value = MAIN_MAP_LOOKUP.lookup(key); 47 | boolean valueExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(value); 48 | if (valueExcluded) { 49 | jsonGenerator.writeNull(); 50 | } else { 51 | jsonGenerator.writeObject(value); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/MainMapResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | public class MainMapResolverFactory implements EventResolverFactory { 20 | 21 | private static final MainMapResolverFactory INSTANCE = new MainMapResolverFactory(); 22 | 23 | private MainMapResolverFactory() {} 24 | 25 | static MainMapResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return MainMapResolver.getName(); 32 | } 33 | 34 | @Override 35 | public MainMapResolver create(EventResolverContext context, String key) { 36 | return new MainMapResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/MapResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.logging.log4j.core.LogEvent; 21 | import org.apache.logging.log4j.message.MapMessage; 22 | import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; 23 | 24 | import java.io.IOException; 25 | 26 | class MapResolver implements EventResolver { 27 | 28 | private final String key; 29 | 30 | static String getName() { 31 | return "map"; 32 | } 33 | 34 | MapResolver(String key) { 35 | this.key = key; 36 | } 37 | 38 | @Override 39 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 40 | if (!(logEvent.getMessage() instanceof MapMessage)) { 41 | jsonGenerator.writeNull(); 42 | } else { 43 | @SuppressWarnings("unchecked") 44 | MapMessage message = (MapMessage) logEvent.getMessage(); 45 | IndexedReadOnlyStringMap map = message.getIndexedReadOnlyStringMap(); 46 | final Object value = map.getValue(key); 47 | jsonGenerator.writeObject(value); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/MapResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | public class MapResolverFactory implements EventResolverFactory { 20 | 21 | private static final MapResolverFactory INSTANCE = new MapResolverFactory(); 22 | 23 | private MapResolverFactory() {} 24 | 25 | static MapResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return MapResolver.getName(); 32 | } 33 | 34 | @Override 35 | public MapResolver create(EventResolverContext context, String key) { 36 | return new MapResolver(key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/MarkerResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.logging.log4j.Marker; 21 | import org.apache.logging.log4j.core.LogEvent; 22 | 23 | import java.io.IOException; 24 | 25 | class MarkerResolver implements EventResolver { 26 | 27 | private static final TemplateResolver NAME_RESOLVER = (logEvent, jsonGenerator) -> { 28 | Marker marker = logEvent.getMarker(); 29 | if (marker == null) { 30 | jsonGenerator.writeNull(); 31 | } else { 32 | jsonGenerator.writeString(marker.getName()); 33 | } 34 | }; 35 | 36 | private final TemplateResolver internalResolver; 37 | 38 | MarkerResolver(String key) { 39 | this.internalResolver = createInternalResolver(key); 40 | } 41 | 42 | private TemplateResolver createInternalResolver(String key) { 43 | switch (key) { 44 | case "name": return NAME_RESOLVER; 45 | } 46 | throw new IllegalArgumentException("unknown key: " + key); 47 | } 48 | 49 | static String getName() { 50 | return "marker"; 51 | } 52 | 53 | @Override 54 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 55 | internalResolver.resolve(logEvent, jsonGenerator); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/MarkerResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class MarkerResolverFactory implements EventResolverFactory { 20 | 21 | private static final MarkerResolverFactory INSTANCE = new MarkerResolverFactory(); 22 | 23 | static MarkerResolverFactory getInstance() { 24 | return INSTANCE; 25 | } 26 | 27 | private MarkerResolverFactory() {} 28 | 29 | @Override 30 | public String getName() { 31 | return MarkerResolver.getName(); 32 | } 33 | 34 | @Override 35 | public MarkerResolver create(EventResolverContext context, String key) { 36 | return new MarkerResolver(key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/MessageResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import com.fasterxml.jackson.databind.JsonNode; 21 | import com.vlkan.log4j2.logstash.layout.util.JsonGenerators; 22 | import org.apache.commons.lang3.StringUtils; 23 | import org.apache.logging.log4j.core.LogEvent; 24 | import org.apache.logging.log4j.message.*; 25 | import org.apache.logging.log4j.util.TriConsumer; 26 | 27 | import java.io.IOException; 28 | 29 | class MessageResolver implements EventResolver { 30 | 31 | private static final String NAME = "message"; 32 | 33 | private static final String[] FORMATS = { "JSON" }; 34 | 35 | private final EventResolverContext context; 36 | 37 | private final String key; 38 | 39 | MessageResolver(EventResolverContext context, String key) { 40 | this.context = context; 41 | this.key = key; 42 | } 43 | 44 | static String getName() { 45 | return NAME; 46 | } 47 | 48 | @Override 49 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 50 | Message message = logEvent.getMessage(); 51 | if (FORMATS[0].equalsIgnoreCase(key)) { 52 | resolveJson(message, jsonGenerator); 53 | } else { 54 | resolveText(message, jsonGenerator); 55 | } 56 | } 57 | 58 | private void resolveText(Message message, JsonGenerator jsonGenerator) throws IOException { 59 | String formattedMessage = resolveText(message); 60 | if (formattedMessage == null) { 61 | jsonGenerator.writeNull(); 62 | } else { 63 | jsonGenerator.writeString(formattedMessage); 64 | } 65 | } 66 | 67 | private String resolveText(Message message) { 68 | String formattedMessage = message.getFormattedMessage(); 69 | boolean messageExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(formattedMessage); 70 | return messageExcluded ? null : formattedMessage; 71 | } 72 | 73 | private void resolveJson(Message message, JsonGenerator jsonGenerator) throws IOException { 74 | 75 | // Try SimpleMessage serializer. 76 | if (writeSimpleMessage(jsonGenerator, message)) { 77 | return; 78 | } 79 | 80 | // Try MultiformatMessage serializer. 81 | if (writeMultiformatMessage(jsonGenerator, message)) { 82 | return; 83 | } 84 | 85 | // Try ObjectMessage serializer. 86 | if (writeObjectMessage(jsonGenerator, message)) { 87 | return; 88 | } 89 | 90 | // Fallback to plain Object write. 91 | writeObject(message, jsonGenerator); 92 | 93 | } 94 | 95 | private boolean writeSimpleMessage(JsonGenerator jsonGenerator, Message message) throws IOException { 96 | 97 | // Check type. 98 | if (!(message instanceof SimpleMessage)) { 99 | return false; 100 | } 101 | SimpleMessage simpleMessage = (SimpleMessage) message; 102 | 103 | // Write message. 104 | String formattedMessage = simpleMessage.getFormattedMessage(); 105 | boolean messageExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(formattedMessage); 106 | if (messageExcluded) { 107 | jsonGenerator.writeNull(); 108 | } else { 109 | jsonGenerator.writeString(formattedMessage); 110 | } 111 | return true; 112 | 113 | } 114 | 115 | private boolean writeMultiformatMessage(JsonGenerator jsonGenerator, Message message) throws IOException { 116 | 117 | // Check type. 118 | if (!(message instanceof MultiformatMessage)) { 119 | return false; 120 | } 121 | MultiformatMessage multiformatMessage = (MultiformatMessage) message; 122 | 123 | // As described in LOG4J2-2703, MapMessage#getFormattedMessage() is 124 | // incorrectly formatting Object's. Hence, we will temporarily work 125 | // around the problem by serializing it ourselves rather than using the 126 | // default provided formatter. 127 | 128 | // Override the provided MapMessage formatter. 129 | if (context.isMapMessageFormatterIgnored() && message instanceof MapMessage) { 130 | @SuppressWarnings("unchecked") 131 | MapMessage mapMessage = (MapMessage) message; 132 | writeMapMessage(jsonGenerator, mapMessage); 133 | return true; 134 | } 135 | 136 | // Check formatter's JSON support. 137 | boolean jsonSupported = false; 138 | String[] formats = multiformatMessage.getFormats(); 139 | for (String format : formats) { 140 | if (FORMATS[0].equalsIgnoreCase(format)) { 141 | jsonSupported = true; 142 | break; 143 | } 144 | } 145 | 146 | // Get the formatted message, if there is any. 147 | if (!jsonSupported) { 148 | writeObject(message, jsonGenerator); 149 | return true; 150 | } 151 | 152 | // Write the formatted JSON. 153 | String messageJson = multiformatMessage.getFormattedMessage(FORMATS); 154 | JsonNode jsonNode = readMessageJson(context, messageJson); 155 | boolean nodeExcluded = isNodeExcluded(jsonNode); 156 | if (nodeExcluded) { 157 | jsonGenerator.writeNull(); 158 | } else { 159 | jsonGenerator.writeTree(jsonNode); 160 | } 161 | return true; 162 | 163 | } 164 | 165 | private static void writeMapMessage(JsonGenerator jsonGenerator, MapMessage mapMessage) throws IOException { 166 | jsonGenerator.writeStartObject(); 167 | mapMessage.forEach(MAP_MESSAGE_ENTRY_WRITER, jsonGenerator); 168 | jsonGenerator.writeEndObject(); 169 | } 170 | 171 | private static TriConsumer MAP_MESSAGE_ENTRY_WRITER = 172 | (key, value, jsonGenerator) -> { 173 | try { 174 | jsonGenerator.writeFieldName(key); 175 | JsonGenerators.writeObject(jsonGenerator, value); 176 | } catch (IOException error) { 177 | throw new RuntimeException("MapMessage entry serialization failure", error); 178 | } 179 | }; 180 | 181 | private static JsonNode readMessageJson(EventResolverContext context, String messageJson) { 182 | try { 183 | return context.getObjectMapper().readTree(messageJson); 184 | } catch (IOException error) { 185 | throw new RuntimeException("JSON message read failure", error); 186 | } 187 | } 188 | 189 | private void writeObject(Message message, JsonGenerator jsonGenerator) throws IOException { 190 | 191 | // Resolve text node. 192 | String formattedMessage = resolveText(message); 193 | if (formattedMessage == null) { 194 | jsonGenerator.writeNull(); 195 | return; 196 | } 197 | 198 | // Put textual representation of the message in an object. 199 | jsonGenerator.writeStartObject(); 200 | jsonGenerator.writeObjectField(NAME, formattedMessage); 201 | jsonGenerator.writeEndObject(); 202 | 203 | } 204 | 205 | private boolean isNodeExcluded(JsonNode jsonNode) { 206 | 207 | if (!context.isEmptyPropertyExclusionEnabled()) { 208 | return false; 209 | } 210 | 211 | if (jsonNode.isNull()) { 212 | return true; 213 | } 214 | 215 | if (jsonNode.isTextual() && StringUtils.isEmpty(jsonNode.asText())) { 216 | return true; 217 | } 218 | 219 | // noinspection RedundantIfStatement 220 | if (jsonNode.isContainerNode() && jsonNode.size() == 0) { 221 | return true; 222 | } 223 | 224 | return false; 225 | 226 | } 227 | 228 | private boolean writeObjectMessage(JsonGenerator jsonGenerator, Message message) throws IOException { 229 | 230 | // Check type. 231 | if (!(message instanceof ObjectMessage)) { 232 | return false; 233 | } 234 | 235 | // Serialize object. 236 | ObjectMessage objectMessage = (ObjectMessage) message; 237 | Object object = objectMessage.getParameter(); 238 | JsonGenerators.writeObject(jsonGenerator, object); 239 | return true; 240 | 241 | } 242 | 243 | } 244 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/MessageResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class MessageResolverFactory implements EventResolverFactory { 20 | 21 | private static final MessageResolverFactory INSTANCE = new MessageResolverFactory(); 22 | 23 | private MessageResolverFactory() {} 24 | 25 | static MessageResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return MessageResolver.getName(); 32 | } 33 | 34 | @Override 35 | public MessageResolver create(EventResolverContext context, String key) { 36 | return new MessageResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/SourceResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.commons.lang3.StringUtils; 21 | import org.apache.logging.log4j.core.LogEvent; 22 | 23 | import java.io.IOException; 24 | 25 | class SourceResolver implements EventResolver { 26 | 27 | private static final EventResolver NULL_RESOLVER = (value, jsonGenerator) -> jsonGenerator.writeNull(); 28 | 29 | private final EventResolver internalResolver; 30 | 31 | SourceResolver(EventResolverContext context, String key) { 32 | this.internalResolver = createInternalResolver(context, key); 33 | } 34 | 35 | private EventResolver createInternalResolver(EventResolverContext context, String key) { 36 | if (!context.isLocationInfoEnabled()) { 37 | return NULL_RESOLVER; 38 | } 39 | switch (key) { 40 | case "className": return createClassNameResolver(context); 41 | case "fileName": return createFileNameResolver(context); 42 | case "lineNumber": return createLineNumberResolver(); 43 | case "methodName": return createMethodNameResolver(context); 44 | } 45 | throw new IllegalArgumentException("unknown key: " + key); 46 | } 47 | 48 | private static EventResolver createClassNameResolver(EventResolverContext context) { 49 | return (logEvent, jsonGenerator) -> { 50 | StackTraceElement logEventSource = logEvent.getSource(); 51 | if (logEventSource != null) { 52 | String sourceClassName = logEventSource.getClassName(); 53 | boolean sourceClassNameExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(sourceClassName); 54 | if (!sourceClassNameExcluded) { 55 | jsonGenerator.writeString(sourceClassName); 56 | return; 57 | } 58 | } 59 | jsonGenerator.writeNull(); 60 | }; 61 | } 62 | 63 | private static EventResolver createFileNameResolver(EventResolverContext context) { 64 | return (logEvent, jsonGenerator) -> { 65 | StackTraceElement logEventSource = logEvent.getSource(); 66 | if (logEventSource != null) { 67 | String sourceFileName = logEventSource.getFileName(); 68 | boolean sourceFileNameExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(sourceFileName); 69 | if (!sourceFileNameExcluded) { 70 | jsonGenerator.writeString(sourceFileName); 71 | return; 72 | } 73 | } 74 | jsonGenerator.writeNull(); 75 | }; 76 | } 77 | 78 | private static EventResolver createLineNumberResolver() { 79 | return (logEvent, jsonGenerator) -> { 80 | StackTraceElement logEventSource = logEvent.getSource(); 81 | if (logEventSource == null) { 82 | jsonGenerator.writeNull(); 83 | } else { 84 | int sourceLineNumber = logEventSource.getLineNumber(); 85 | jsonGenerator.writeNumber(sourceLineNumber); 86 | } 87 | }; 88 | } 89 | 90 | private static EventResolver createMethodNameResolver(EventResolverContext context) { 91 | return (logEvent, jsonGenerator) -> { 92 | StackTraceElement logEventSource = logEvent.getSource(); 93 | if (logEventSource != null) { 94 | String sourceMethodName = logEventSource.getMethodName(); 95 | boolean sourceMethodNameExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(sourceMethodName); 96 | if (!sourceMethodNameExcluded) { 97 | jsonGenerator.writeString(sourceMethodName); 98 | return; 99 | } 100 | } 101 | jsonGenerator.writeNull(); 102 | }; 103 | } 104 | 105 | static String getName() { 106 | return "source"; 107 | } 108 | 109 | @Override 110 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 111 | internalResolver.resolve(logEvent, jsonGenerator); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/SourceResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class SourceResolverFactory implements EventResolverFactory { 20 | 21 | private static final SourceResolverFactory INSTANCE = new SourceResolverFactory(); 22 | 23 | private SourceResolverFactory() {} 24 | 25 | static SourceResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return SourceResolver.getName(); 32 | } 33 | 34 | @Override 35 | public SourceResolver create(EventResolverContext context, String key) { 36 | return new SourceResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/StackTraceElementObjectResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | 21 | import java.io.IOException; 22 | 23 | class StackTraceElementObjectResolver implements TemplateResolver { 24 | 25 | private static final TemplateResolver CLASS_NAME_RESOLVER = 26 | (stackTraceElement, jsonGenerator) -> jsonGenerator.writeString(stackTraceElement.getClassName()); 27 | 28 | private static final TemplateResolver METHOD_NAME_RESOLVER = 29 | (stackTraceElement, jsonGenerator) -> jsonGenerator.writeString(stackTraceElement.getMethodName()); 30 | 31 | private static final TemplateResolver FILE_NAME_RESOLVER = 32 | (stackTraceElement, jsonGenerator) -> jsonGenerator.writeString(stackTraceElement.getFileName()); 33 | 34 | private static final TemplateResolver LINE_NUMBER_RESOLVER = 35 | (stackTraceElement, jsonGenerator) -> jsonGenerator.writeNumber(stackTraceElement.getLineNumber()); 36 | 37 | private final TemplateResolver internalResolver; 38 | 39 | StackTraceElementObjectResolver(String key) { 40 | this.internalResolver = createInternalResolver(key); 41 | } 42 | 43 | private TemplateResolver createInternalResolver(String key) { 44 | switch (key) { 45 | case "className": return CLASS_NAME_RESOLVER; 46 | case "methodName": return METHOD_NAME_RESOLVER; 47 | case "fileName": return FILE_NAME_RESOLVER; 48 | case "lineNumber": return LINE_NUMBER_RESOLVER; 49 | } 50 | throw new IllegalArgumentException("unknown key: " + key); 51 | } 52 | 53 | static String getName() { 54 | return "stackTraceElement"; 55 | } 56 | 57 | @Override 58 | public void resolve(StackTraceElement stackTraceElement, JsonGenerator jsonGenerator) throws IOException { 59 | internalResolver.resolve(stackTraceElement, jsonGenerator); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/StackTraceElementObjectResolverContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import org.apache.commons.lang3.Validate; 21 | import org.apache.logging.log4j.core.lookup.StrSubstitutor; 22 | 23 | import java.util.Map; 24 | 25 | public class StackTraceElementObjectResolverContext implements TemplateResolverContext { 26 | 27 | private final ObjectMapper objectMapper; 28 | 29 | private final StrSubstitutor substitutor; 30 | 31 | private final boolean emptyPropertyExclusionEnabled; 32 | 33 | private StackTraceElementObjectResolverContext(Builder builder) { 34 | this.objectMapper = builder.objectMapper; 35 | this.substitutor = builder.substitutor; 36 | this.emptyPropertyExclusionEnabled = builder.emptyPropertyExclusionEnabled; 37 | } 38 | 39 | @Override 40 | public Class getContextClass() { 41 | return StackTraceElementObjectResolverContext.class; 42 | } 43 | 44 | @Override 45 | public Map>> getResolverFactoryByName() { 46 | return StackTraceElementObjectResolverFactories.getResolverFactoryByName(); 47 | } 48 | 49 | @Override 50 | public ObjectMapper getObjectMapper() { 51 | return objectMapper; 52 | } 53 | 54 | @Override 55 | public StrSubstitutor getSubstitutor() { 56 | return substitutor; 57 | } 58 | 59 | @Override 60 | public boolean isEmptyPropertyExclusionEnabled() { 61 | return emptyPropertyExclusionEnabled; 62 | } 63 | 64 | public static Builder newBuilder() { 65 | return new Builder(); 66 | } 67 | 68 | public static class Builder { 69 | 70 | private ObjectMapper objectMapper; 71 | 72 | private StrSubstitutor substitutor; 73 | 74 | private boolean emptyPropertyExclusionEnabled; 75 | 76 | private Builder() { 77 | // Do nothing. 78 | } 79 | 80 | public Builder setObjectMapper(ObjectMapper objectMapper) { 81 | this.objectMapper = objectMapper; 82 | return this; 83 | } 84 | 85 | public Builder setSubstitutor(StrSubstitutor substitutor) { 86 | this.substitutor = substitutor; 87 | return this; 88 | } 89 | 90 | public Builder setEmptyPropertyExclusionEnabled(boolean emptyPropertyExclusionEnabled) { 91 | this.emptyPropertyExclusionEnabled = emptyPropertyExclusionEnabled; 92 | return this; 93 | } 94 | 95 | public StackTraceElementObjectResolverContext build() { 96 | validate(); 97 | return new StackTraceElementObjectResolverContext(this); 98 | } 99 | 100 | private void validate() { 101 | Validate.notNull(objectMapper, "objectMapper"); 102 | Validate.notNull(substitutor, "substitutor"); 103 | } 104 | 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/StackTraceElementObjectResolverFactories.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import java.util.Collections; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | enum StackTraceElementObjectResolverFactories {; 24 | 25 | private static final Map>> RESOLVER_FACTORY_BY_NAME = createResolverFactoryByName(); 26 | 27 | private static Map>> createResolverFactoryByName() { 28 | Map>> resolverFactoryByName = new LinkedHashMap<>(); 29 | StackTraceElementObjectResolverFactory stackTraceElementObjectResolverFactory = StackTraceElementObjectResolverFactory.getInstance(); 30 | resolverFactoryByName.put(stackTraceElementObjectResolverFactory.getName(), stackTraceElementObjectResolverFactory); 31 | return Collections.unmodifiableMap(resolverFactoryByName); 32 | } 33 | 34 | static Map>> getResolverFactoryByName() { 35 | return RESOLVER_FACTORY_BY_NAME; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/StackTraceElementObjectResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class StackTraceElementObjectResolverFactory implements TemplateResolverFactory { 20 | 21 | private static final StackTraceElementObjectResolverFactory INSTANCE = new StackTraceElementObjectResolverFactory(); 22 | 23 | private StackTraceElementObjectResolverFactory() {} 24 | 25 | public static StackTraceElementObjectResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return StackTraceElementObjectResolver.getName(); 32 | } 33 | 34 | @Override 35 | public StackTraceElementObjectResolver create(StackTraceElementObjectResolverContext context, String key) { 36 | return new StackTraceElementObjectResolver(key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/StackTraceObjectResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | 21 | import java.io.IOException; 22 | 23 | class StackTraceObjectResolver implements StackTraceResolver { 24 | 25 | private final TemplateResolver stackTraceElementResolver; 26 | 27 | StackTraceObjectResolver(TemplateResolver stackTraceElementResolver) { 28 | this.stackTraceElementResolver = stackTraceElementResolver; 29 | } 30 | 31 | @Override 32 | public void resolve(Throwable throwable, JsonGenerator jsonGenerator) throws IOException { 33 | 34 | // Extract the stack trace. 35 | StackTraceElement[] stackTraceElements; 36 | Throwable lastThrowable = throwable; 37 | while (true) { 38 | try { 39 | stackTraceElements = lastThrowable.getStackTrace(); 40 | break; 41 | } 42 | // It is indeed not a good practice to catch `Throwable`s, but what 43 | // one should do while trying to access the stack trace of a 44 | // failure? Hence, if `Throwable#getStackTrace()` fails for some 45 | // reason, at least try to access to the reason of the failure. 46 | catch (Throwable newThrowable) { 47 | lastThrowable = newThrowable; 48 | } 49 | } 50 | 51 | // Resolve the stack trace elements. 52 | if (stackTraceElements.length == 0) { 53 | jsonGenerator.writeNull(); 54 | } else { 55 | jsonGenerator.writeStartArray(); 56 | // noinspection ForLoopReplaceableByForEach (avoid iterator instantiation) 57 | for (int stackTraceElementIndex = 0; stackTraceElementIndex < stackTraceElements.length; stackTraceElementIndex++) { 58 | StackTraceElement stackTraceElement = stackTraceElements[stackTraceElementIndex]; 59 | stackTraceElementResolver.resolve(stackTraceElement, jsonGenerator); 60 | } 61 | jsonGenerator.writeEndArray(); 62 | } 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/StackTraceResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | interface StackTraceResolver extends TemplateResolver {} 20 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/StackTraceTextResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import com.vlkan.log4j2.logstash.layout.util.BufferedPrintWriter; 21 | import org.apache.logging.log4j.util.Constants; 22 | 23 | import java.io.IOException; 24 | import java.util.function.Supplier; 25 | 26 | class StackTraceTextResolver implements StackTraceResolver { 27 | 28 | private final Supplier writerSupplier; 29 | 30 | private final ThreadLocal writerRef; 31 | 32 | StackTraceTextResolver(int writerCapacity) { 33 | this.writerSupplier = () -> BufferedPrintWriter.ofCapacity(writerCapacity); 34 | this.writerRef = Constants.ENABLE_THREADLOCALS 35 | ? ThreadLocal.withInitial(writerSupplier) 36 | : null; 37 | } 38 | 39 | @Override 40 | public void resolve(Throwable throwable, JsonGenerator jsonGenerator) throws IOException { 41 | BufferedPrintWriter writer = getResetWriter(); 42 | Throwable lastThrowable = throwable; 43 | while (true) { 44 | try { 45 | lastThrowable.printStackTrace(writer); 46 | break; 47 | } 48 | // It is indeed not a good practice to catch `Throwable`s, but what 49 | // one should do while trying to dump the stack trace of a failure? 50 | // Hence, if `Throwable#printStackTrace(PrintWriter)` fails for some 51 | // reason, at least try to dump the reason of the failure. 52 | catch (Throwable newThrowable) { 53 | writer.close(); 54 | lastThrowable = newThrowable; 55 | } 56 | } 57 | jsonGenerator.writeString(writer.getBuffer(), 0, writer.getPosition()); 58 | } 59 | 60 | private BufferedPrintWriter getResetWriter() { 61 | BufferedPrintWriter writer; 62 | if (Constants.ENABLE_THREADLOCALS) { 63 | writer = writerRef.get(); 64 | writer.close(); 65 | } else { 66 | writer = writerSupplier.get(); 67 | } 68 | return writer; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/TemplateResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | 21 | import java.io.IOException; 22 | 23 | public interface TemplateResolver { 24 | 25 | void resolve(V value, JsonGenerator jsonGenerator) throws IOException; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/TemplateResolverContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import org.apache.logging.log4j.core.lookup.StrSubstitutor; 21 | 22 | import java.util.Map; 23 | 24 | interface TemplateResolverContext> { 25 | 26 | Class getContextClass(); 27 | 28 | Map>> getResolverFactoryByName(); 29 | 30 | ObjectMapper getObjectMapper(); 31 | 32 | StrSubstitutor getSubstitutor(); 33 | 34 | boolean isEmptyPropertyExclusionEnabled(); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/TemplateResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | interface TemplateResolverFactory, R extends TemplateResolver> { 20 | 21 | String getName(); 22 | 23 | R create(C context, String key); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ThreadResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.commons.lang3.StringUtils; 21 | import org.apache.logging.log4j.core.LogEvent; 22 | 23 | import java.io.IOException; 24 | 25 | class ThreadResolver implements EventResolver { 26 | 27 | private final EventResolver internalResolver; 28 | 29 | ThreadResolver(EventResolverContext context, String key) { 30 | this.internalResolver = createInternalResolver(context, key); 31 | } 32 | 33 | private static EventResolver createInternalResolver(EventResolverContext context, String key) { 34 | switch (key) { 35 | case "name": return createNameResolver(context); 36 | case "id": return createIdResolver(); 37 | case "priority": return createPriorityResolver(); 38 | } 39 | throw new IllegalArgumentException("unknown key: " + key); 40 | } 41 | 42 | private static EventResolver createNameResolver(EventResolverContext context) { 43 | return (logEvent, jsonGenerator) -> { 44 | String threadName = logEvent.getThreadName(); 45 | boolean threadNameExcluded = context.isEmptyPropertyExclusionEnabled() && StringUtils.isEmpty(threadName); 46 | if (threadNameExcluded) { 47 | jsonGenerator.writeNull(); 48 | } else { 49 | jsonGenerator.writeString(threadName); 50 | } 51 | }; 52 | } 53 | 54 | private static EventResolver createIdResolver() { 55 | return (logEvent, jsonGenerator) -> { 56 | long threadId = logEvent.getThreadId(); 57 | jsonGenerator.writeNumber(threadId); 58 | }; 59 | } 60 | 61 | private static EventResolver createPriorityResolver() { 62 | return (logEvent, jsonGenerator) -> { 63 | int threadPriority = logEvent.getThreadPriority(); 64 | jsonGenerator.writeNumber(threadPriority); 65 | }; 66 | } 67 | 68 | static String getName() { 69 | return "thread"; 70 | } 71 | 72 | @Override 73 | public void resolve(LogEvent logEvent, JsonGenerator jsonGenerator) throws IOException { 74 | internalResolver.resolve(logEvent, jsonGenerator); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/ThreadResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class ThreadResolverFactory implements EventResolverFactory { 20 | 21 | private static final ThreadResolverFactory INSTANCE = new ThreadResolverFactory(); 22 | 23 | private ThreadResolverFactory() {} 24 | 25 | static ThreadResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return ThreadResolver.getName(); 32 | } 33 | 34 | @Override 35 | public ThreadResolver create(EventResolverContext context, String key) { 36 | return new ThreadResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/resolver/TimestampResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.resolver; 18 | 19 | class TimestampResolverFactory implements EventResolverFactory { 20 | 21 | private static final TimestampResolverFactory INSTANCE = new TimestampResolverFactory(); 22 | 23 | private TimestampResolverFactory() {} 24 | 25 | static TimestampResolverFactory getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return TimestampResolver.getName(); 32 | } 33 | 34 | @Override 35 | public TimestampResolver create(EventResolverContext context, String key) { 36 | return new TimestampResolver(context, key); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/util/AutoCloseables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | public enum AutoCloseables {; 20 | 21 | public static void closeUnchecked(AutoCloseable closeable) { 22 | try { 23 | closeable.close(); 24 | } catch (Exception error) { 25 | throw new RuntimeException(error); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/util/BufferedPrintWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | import java.io.PrintWriter; 20 | 21 | public final class BufferedPrintWriter extends PrintWriter { 22 | 23 | private final BufferedWriter bufferedWriter; 24 | 25 | private BufferedPrintWriter(BufferedWriter bufferedWriter) { 26 | super(bufferedWriter, false); 27 | this.bufferedWriter = bufferedWriter; 28 | } 29 | 30 | public static BufferedPrintWriter ofCapacity(int capacity) { 31 | BufferedWriter bufferedWriter = new BufferedWriter(capacity); 32 | return new BufferedPrintWriter(bufferedWriter); 33 | } 34 | 35 | public char[] getBuffer() { 36 | return bufferedWriter.getBuffer(); 37 | } 38 | 39 | public int getPosition() { 40 | return bufferedWriter.getPosition(); 41 | } 42 | 43 | public int getCapacity() { 44 | return bufferedWriter.getCapacity(); 45 | } 46 | 47 | public boolean isOverflow() { 48 | return bufferedWriter.isOverflow(); 49 | } 50 | 51 | @Override 52 | public void close() { 53 | bufferedWriter.close(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/util/BufferedWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | import java.io.Writer; 20 | 21 | public final class BufferedWriter extends Writer { 22 | 23 | private final char[] buffer; 24 | 25 | private int position; 26 | 27 | private boolean overflow; 28 | 29 | BufferedWriter(int capacity) { 30 | this.buffer = new char[capacity]; 31 | this.position = 0; 32 | this.overflow = false; 33 | } 34 | 35 | char[] getBuffer() { 36 | return buffer; 37 | } 38 | 39 | int getPosition() { 40 | return position; 41 | } 42 | 43 | int getCapacity() { 44 | return buffer.length; 45 | } 46 | 47 | boolean isOverflow() { 48 | return overflow; 49 | } 50 | 51 | @Override 52 | public void write(char[] source, int offset, int length) { 53 | if (!overflow) { 54 | int limit = buffer.length - position; 55 | if (length > limit) { 56 | overflow = true; 57 | System.arraycopy(source, offset, buffer, position, limit); 58 | position = buffer.length; 59 | } else { 60 | System.arraycopy(source, offset, buffer, position, length); 61 | position += length; 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public void flush() {} 68 | 69 | @Override 70 | public void close() { 71 | position = 0; 72 | overflow = false; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/util/ByteBufferDestinations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | import org.apache.logging.log4j.core.layout.ByteBufferDestination; 20 | 21 | import java.nio.ByteBuffer; 22 | 23 | public enum ByteBufferDestinations {; 24 | 25 | /** 26 | * Back ported from {@link org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper} introduced in 2.9. 27 | */ 28 | public static void writeToUnsynchronized(ByteBuffer source, ByteBufferDestination destination) { 29 | ByteBuffer destBuff = destination.getByteBuffer(); 30 | while (source.remaining() > destBuff.remaining()) { 31 | int originalLimit = source.limit(); 32 | source.limit(Math.min(source.limit(), source.position() + destBuff.remaining())); 33 | destBuff.put(source); 34 | source.limit(originalLimit); 35 | destBuff = destination.drain(destBuff); 36 | } 37 | destBuff.put(source); 38 | // No drain in the end. 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/util/ByteBufferOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | import java.io.OutputStream; 20 | import java.nio.Buffer; 21 | import java.nio.ByteBuffer; 22 | import java.nio.charset.Charset; 23 | 24 | public class ByteBufferOutputStream extends OutputStream { 25 | 26 | private final ByteBuffer byteBuffer; 27 | 28 | public ByteBufferOutputStream(int byteCount) { 29 | this.byteBuffer = ByteBuffer.allocate(byteCount); 30 | } 31 | 32 | public ByteBuffer getByteBuffer() { 33 | return byteBuffer; 34 | } 35 | 36 | @Override 37 | public void write(int codeInt) { 38 | byte codeByte = (byte) codeInt; 39 | byteBuffer.put(codeByte); 40 | } 41 | 42 | @Override 43 | public void write(byte[] buf) { 44 | byteBuffer.put(buf); 45 | } 46 | 47 | @Override 48 | public void write(byte[] buf, int off, int len) { 49 | byteBuffer.put(buf, off, len); 50 | } 51 | 52 | public byte[] toByteArray() { 53 | @SuppressWarnings("RedundantCast") // for Java 8 compatibility 54 | int size = ((Buffer) byteBuffer).position(); 55 | byte[] buffer = new byte[size]; 56 | System.arraycopy(byteBuffer.array(), 0, buffer, 0, size); 57 | return buffer; 58 | } 59 | 60 | public String toString(Charset charset) { 61 | // noinspection RedundantCast (for Java 8 compatibility) 62 | return new String(byteBuffer.array(), 0, ((Buffer) byteBuffer).position(), charset); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/util/JsonGenerators.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import org.apache.logging.log4j.core.util.Constants; 21 | 22 | import java.io.IOException; 23 | import java.math.BigDecimal; 24 | 25 | public enum JsonGenerators {; 26 | 27 | private static final class DoubleWriterContext { 28 | 29 | private static final int MAX_BUFFER_LENGTH = 30 | String.format("%d.%d", Long.MAX_VALUE, Integer.MAX_VALUE).length() 31 | + 1; 32 | 33 | private final StringBuilder builder = new StringBuilder(); 34 | 35 | private char[] buffer = new char[MAX_BUFFER_LENGTH]; 36 | 37 | } 38 | 39 | private static final ThreadLocal DOUBLE_WRITER_CONTEXT_REF = 40 | Constants.ENABLE_THREADLOCALS 41 | ? ThreadLocal.withInitial(DoubleWriterContext::new) 42 | : null; 43 | 44 | public static void writeDouble(JsonGenerator jsonGenerator, long integralPart, int fractionalPart) throws IOException { 45 | if (fractionalPart < 0) { 46 | throw new IllegalArgumentException("negative fraction"); 47 | } else if (fractionalPart == 0) { 48 | jsonGenerator.writeNumber(integralPart); 49 | } else if (Constants.ENABLE_THREADLOCALS) { 50 | DoubleWriterContext context = DOUBLE_WRITER_CONTEXT_REF.get(); 51 | context.builder.setLength(0); 52 | context.builder.append(integralPart); 53 | context.builder.append('.'); 54 | context.builder.append(fractionalPart); 55 | int length = context.builder.length(); 56 | context.builder.getChars(0, length, context.buffer, 0); 57 | // jackson-core version >=2.10.2 is required for the following line 58 | // to avoid FasterXML/jackson-core#588 bug. 59 | jsonGenerator.writeRawValue(context.buffer, 0, length); 60 | } else { 61 | String formattedNumber = "" + integralPart + '.' + fractionalPart; 62 | jsonGenerator.writeNumber(formattedNumber); 63 | } 64 | } 65 | 66 | /** 67 | * Writes given object, preferably using GC-free writers. 68 | */ 69 | public static void writeObject(JsonGenerator jsonGenerator, Object object) throws IOException { 70 | 71 | if (object == null) { 72 | jsonGenerator.writeNull(); 73 | return; 74 | } 75 | 76 | if (object instanceof String) { 77 | jsonGenerator.writeString((String) object); 78 | return; 79 | } 80 | 81 | if (object instanceof Short) { 82 | jsonGenerator.writeNumber((Short) object); 83 | return; 84 | } 85 | 86 | if (object instanceof Integer) { 87 | jsonGenerator.writeNumber((Integer) object); 88 | return; 89 | } 90 | 91 | if (object instanceof Long) { 92 | jsonGenerator.writeNumber((Long) object); 93 | return; 94 | } 95 | 96 | if (object instanceof BigDecimal) { 97 | jsonGenerator.writeNumber((BigDecimal) object); 98 | return; 99 | } 100 | 101 | if (object instanceof Float) { 102 | jsonGenerator.writeNumber((Float) object); // Not GC-free! 103 | return; 104 | } 105 | 106 | if (object instanceof Double) { 107 | jsonGenerator.writeNumber((Double) object); // Not GC-free! 108 | return; 109 | } 110 | 111 | if (object instanceof byte[]) { 112 | jsonGenerator.writeBinary((byte[]) object); 113 | return; 114 | } 115 | 116 | jsonGenerator.writeObject(object); 117 | 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/util/Throwables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | public enum Throwables {; 20 | 21 | public static Throwable getRootCause(Throwable throwable) { 22 | 23 | // Keep a second pointer that slowly walks the causal chain. If the fast pointer ever catches 24 | // the slower pointer, then there's a loop. 25 | Throwable slowPointer = throwable; 26 | boolean advanceSlowPointer = false; 27 | 28 | Throwable cause; 29 | while ((cause = throwable.getCause()) != null) { 30 | throwable = cause; 31 | if (throwable == slowPointer) { 32 | throw new IllegalArgumentException("loop in causal chain", throwable); 33 | } 34 | if (advanceSlowPointer) { 35 | slowPointer = slowPointer.getCause(); 36 | } 37 | advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration 38 | } 39 | return throwable; 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /layout/src/main/java/com/vlkan/log4j2/logstash/layout/util/Uris.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | import org.apache.commons.lang3.Validate; 20 | 21 | import java.io.*; 22 | import java.net.URI; 23 | import java.net.URL; 24 | import java.nio.charset.StandardCharsets; 25 | 26 | public enum Uris {; 27 | 28 | public static String readUri(String spec) { 29 | try { 30 | return unsafeReadUri(spec); 31 | } catch (Exception error) { 32 | String message = String.format("failed reading URI (spec=%s)", spec); 33 | throw new RuntimeException(message, error); 34 | } 35 | } 36 | 37 | private static String unsafeReadUri(String spec) throws Exception { 38 | URI uri = new URI(spec); 39 | String uriScheme = uri.getScheme().toLowerCase(); 40 | switch (uriScheme) { 41 | case "classpath": 42 | return readClassPathUri(uri); 43 | case "file": 44 | return readFileUri(uri); 45 | default: { 46 | String message = String.format("unknown URI scheme (spec='%s')", spec); 47 | throw new IllegalArgumentException(message); 48 | } 49 | } 50 | 51 | } 52 | 53 | private static String readFileUri(URI uri) throws IOException { 54 | File file = new File(uri); 55 | try (FileReader fileReader = new FileReader(file)) { 56 | return consumeReader(fileReader); 57 | } 58 | } 59 | 60 | private static String readClassPathUri(URI uri) throws IOException { 61 | String spec = uri.toString(); 62 | String path = spec.substring("classpath:".length()); 63 | URL resource = Uris.class.getClassLoader().getResource(path); 64 | Validate.notNull(resource, "could not locate classpath resource (path=%s)", path); 65 | try (InputStream inputStream = resource.openStream()) { 66 | try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { 67 | return consumeReader(reader); 68 | } 69 | } 70 | } 71 | 72 | private static String consumeReader(Reader reader) throws IOException { 73 | StringBuilder builder = new StringBuilder(); 74 | try (BufferedReader bufferedReader = new BufferedReader(reader)) { 75 | String line; 76 | while ((line = bufferedReader.readLine()) != null) { 77 | builder.append(line); 78 | } 79 | } 80 | return builder.toString(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /layout/src/main/resources/EcsLayout.json: -------------------------------------------------------------------------------- 1 | { 2 | "@timestamp": "${json:timestamp}", 3 | "log.level": "${json:level}", 4 | "message": "${json:message}", 5 | "process.thread.name": "${json:thread:name}", 6 | "log.logger": "${json:logger:name}", 7 | "labels": "${json:mdc}", 8 | "tags": "${json:ndc}", 9 | "error.type": "${json:exception:className}", 10 | "error.message": "${json:exception:message}", 11 | "error.stack_trace": "${json:exception:stackTrace:text}" 12 | } 13 | -------------------------------------------------------------------------------- /layout/src/main/resources/GelfLayout.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.1", 3 | "host": "${hostName}", 4 | "short_message": "${json:message}", 5 | "full_message": "${json:exception:stackTrace:text}", 6 | "timestamp": "${json:timestamp:epoch:divisor=1e9}", 7 | "level": "${json:level:severity:code}", 8 | "_logger": "${json:logger:name}", 9 | "_thread": "${json:thread:name}" 10 | } 11 | -------------------------------------------------------------------------------- /layout/src/main/resources/Log4j2StackTraceElementLayout.json: -------------------------------------------------------------------------------- 1 | { 2 | "class": "${json:stackTraceElement:className}", 3 | "method": "${json:stackTraceElement:methodName}", 4 | "file": "${json:stackTraceElement:fileName}", 5 | "line": "${json:stackTraceElement:lineNumber}" 6 | } 7 | -------------------------------------------------------------------------------- /layout/src/main/resources/LogstashJsonEventLayoutV1.json: -------------------------------------------------------------------------------- 1 | { 2 | "mdc": "${json:mdc}", 3 | "exception": { 4 | "exception_class": "${json:exception:className}", 5 | "exception_message": "${json:exception:message}", 6 | "stacktrace": "${json:exception:stackTrace:text}" 7 | }, 8 | "line_number": "${json:source:lineNumber}", 9 | "class": "${json:source:className}", 10 | "@version": 1, 11 | "source_host": "${hostName}", 12 | "message": "${json:message}", 13 | "thread_name": "${json:thread:name}", 14 | "@timestamp": "${json:timestamp}", 15 | "level": "${json:level}", 16 | "file": "${json:source:fileName}", 17 | "method": "${json:source:methodName}", 18 | "logger_name": "${json:logger:name}" 19 | } 20 | -------------------------------------------------------------------------------- /layout/src/test/java/com/vlkan/log4j2/logstash/layout/FixedByteBufferDestination.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | import org.apache.logging.log4j.core.layout.ByteBufferDestination; 20 | 21 | import java.nio.BufferOverflowException; 22 | import java.nio.ByteBuffer; 23 | 24 | class FixedByteBufferDestination implements ByteBufferDestination { 25 | 26 | private final ByteBuffer byteBuffer; 27 | 28 | FixedByteBufferDestination(int maxByteCount) { 29 | this.byteBuffer = ByteBuffer.allocate(maxByteCount); 30 | } 31 | 32 | @Override 33 | public ByteBuffer getByteBuffer() { 34 | return byteBuffer; 35 | } 36 | 37 | @Override 38 | public ByteBuffer drain(ByteBuffer sourceByteBuffer) { 39 | if (byteBuffer != sourceByteBuffer) { 40 | sourceByteBuffer.flip(); 41 | byteBuffer.put(sourceByteBuffer); 42 | } else if (byteBuffer.remaining() == 0) { 43 | throw new BufferOverflowException(); 44 | } 45 | return byteBuffer; 46 | } 47 | 48 | @Override 49 | public void writeBytes(ByteBuffer sourceByteBuffer) { 50 | byteBuffer.put(sourceByteBuffer); 51 | } 52 | 53 | @Override 54 | public void writeBytes(byte[] data, int offset, int length) { 55 | byteBuffer.put(data,offset,length); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /layout/src/test/java/com/vlkan/log4j2/logstash/layout/LogEventFixture.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | import org.apache.logging.log4j.Level; 20 | import org.apache.logging.log4j.core.LogEvent; 21 | import org.apache.logging.log4j.core.impl.ContextDataFactory; 22 | import org.apache.logging.log4j.core.impl.Log4jLogEvent; 23 | import org.apache.logging.log4j.message.SimpleMessage; 24 | import org.apache.logging.log4j.spi.MutableThreadContextStack; 25 | import org.apache.logging.log4j.spi.ThreadContextStack; 26 | import org.apache.logging.log4j.util.StringMap; 27 | 28 | import java.io.IOException; 29 | import java.math.BigDecimal; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | enum LogEventFixture {; 34 | 35 | private static final int TIME_OVERLAPPING_CONSECUTIVE_EVENT_COUNT = 10; 36 | 37 | static List createLiteLogEvents(int logEventCount) { 38 | List logEvents = new ArrayList<>(logEventCount); 39 | long startTimeMillis = System.currentTimeMillis(); 40 | for (int logEventIndex = 0; logEventIndex < logEventCount; logEventIndex++) { 41 | String logEventId = String.valueOf(logEventIndex); 42 | long logEventTimeMillis = createLogEventTimeMillis(startTimeMillis, logEventIndex); 43 | LogEvent logEvent = LogEventFixture.createLiteLogEvent(logEventId, logEventTimeMillis); 44 | logEvents.add(logEvent); 45 | } 46 | return logEvents; 47 | } 48 | 49 | private static LogEvent createLiteLogEvent(String id, long timeMillis) { 50 | SimpleMessage message = new SimpleMessage("lite LogEvent message " + id); 51 | Level level = Level.DEBUG; 52 | String loggerFqcn = "f.q.c.n" + id; 53 | String loggerName = "a.B" + id; 54 | long nanoTime = timeMillis * 2; 55 | return Log4jLogEvent 56 | .newBuilder() 57 | .setLoggerName(loggerName) 58 | .setLoggerFqcn(loggerFqcn) 59 | .setLevel(level) 60 | .setMessage(message) 61 | .setTimeMillis(timeMillis) 62 | .setNanoTime(nanoTime) 63 | .build(); 64 | } 65 | 66 | static List createFullLogEvents(int logEventCount) { 67 | List logEvents = new ArrayList<>(logEventCount); 68 | long startTimeMillis = System.currentTimeMillis(); 69 | for (int logEventIndex = 0; logEventIndex < logEventCount; logEventIndex++) { 70 | String logEventId = String.valueOf(logEventIndex); 71 | long logEventTimeMillis = createLogEventTimeMillis(startTimeMillis, logEventIndex); 72 | LogEvent logEvent = LogEventFixture.createFullLogEvent(logEventId, logEventTimeMillis); 73 | logEvents.add(logEvent); 74 | } 75 | return logEvents; 76 | } 77 | 78 | private static long createLogEventTimeMillis(long startTimeMillis, int logEventIndex) { 79 | // Create event time repeating every certain number of consecutive 80 | // events. This is better aligned with the real-world use case and 81 | // gives surface to timestamp formatter caches to perform their 82 | // magic, which is implemented for almost all layouts. 83 | return startTimeMillis + logEventIndex / TIME_OVERLAPPING_CONSECUTIVE_EVENT_COUNT; 84 | } 85 | 86 | private static LogEvent createFullLogEvent(String id, long timeMillis) { 87 | 88 | // Create exception. 89 | Exception sourceHelper = new Exception(); 90 | sourceHelper.fillInStackTrace(); 91 | Exception cause = new NullPointerException("testNPEx-" + id); 92 | sourceHelper.fillInStackTrace(); 93 | StackTraceElement source = sourceHelper.getStackTrace()[0]; 94 | IOException ioException = new IOException("testIOEx-" + id, cause); 95 | ioException.addSuppressed(new IndexOutOfBoundsException("I am suppressed exception 1" + id)); 96 | ioException.addSuppressed(new IndexOutOfBoundsException("I am suppressed exception 2" + id)); 97 | 98 | // Create rest of the event attributes. 99 | SimpleMessage message = new SimpleMessage("full LogEvent message " + id); 100 | StringMap contextData = createContextData(id); 101 | ThreadContextStack contextStack = createContextStack(id); 102 | int threadId = id.hashCode(); 103 | String threadName = "MyThreadName" + id; 104 | int threadPriority = threadId % 10; 105 | Level level = Level.DEBUG; 106 | String loggerFqcn = "f.q.c.n" + id; 107 | String loggerName = "a.B" + id; 108 | long nanoTime = timeMillis * 2; 109 | 110 | return Log4jLogEvent 111 | .newBuilder() 112 | .setLoggerName(loggerName) 113 | .setLoggerFqcn(loggerFqcn) 114 | .setLevel(level) 115 | .setMessage(message) 116 | .setThrown(ioException) 117 | .setContextData(contextData) 118 | .setContextStack(contextStack) 119 | .setThreadId(threadId) 120 | .setThreadName(threadName) 121 | .setThreadPriority(threadPriority) 122 | .setSource(source) 123 | .setTimeMillis(timeMillis) 124 | .setNanoTime(nanoTime) 125 | .build(); 126 | 127 | } 128 | 129 | private static StringMap createContextData(String id) { 130 | StringMap contextData = ContextDataFactory.createContextData(); 131 | contextData.putValue("MDC.String." + id, "String"); 132 | contextData.putValue("MDC.BigDecimal." + id, BigDecimal.valueOf(Math.PI)); 133 | contextData.putValue("MDC.Integer." + id, 10); 134 | contextData.putValue("MDC.Long." + id, Long.MAX_VALUE); 135 | return contextData; 136 | } 137 | 138 | private static ThreadContextStack createContextStack(String id) { 139 | ThreadContextStack contextStack = new MutableThreadContextStack(); 140 | contextStack.clear(); 141 | contextStack.push("stack_msg1" + id); 142 | contextStack.add("stack_msg2" + id); 143 | return contextStack; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /layout/src/test/java/com/vlkan/log4j2/logstash/layout/LogstashLayoutConcurrentEncodeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | import org.apache.logging.log4j.core.LogEvent; 20 | import org.apache.logging.log4j.core.config.Configuration; 21 | import org.apache.logging.log4j.core.config.DefaultConfiguration; 22 | import org.apache.logging.log4j.core.impl.Log4jLogEvent; 23 | import org.apache.logging.log4j.core.layout.ByteBufferDestination; 24 | import org.apache.logging.log4j.message.SimpleMessage; 25 | import org.assertj.core.api.Assertions; 26 | import org.junit.Test; 27 | 28 | import java.nio.ByteBuffer; 29 | import java.util.List; 30 | import java.util.Random; 31 | import java.util.concurrent.atomic.AtomicInteger; 32 | import java.util.concurrent.atomic.AtomicLong; 33 | import java.util.concurrent.atomic.AtomicReference; 34 | import java.util.stream.Collectors; 35 | import java.util.stream.IntStream; 36 | 37 | public class LogstashLayoutConcurrentEncodeTest { 38 | 39 | private static class ConcurrentAccessError extends RuntimeException { 40 | 41 | private ConcurrentAccessError(int concurrentAccessCount) { 42 | super("concurrentAccessCount=" + concurrentAccessCount); 43 | } 44 | 45 | } 46 | 47 | private static class ConcurrentAccessDetectingByteBufferDestination extends BlackHoleByteBufferDestination { 48 | 49 | private final AtomicInteger concurrentAccessCounter = new AtomicInteger(0); 50 | 51 | ConcurrentAccessDetectingByteBufferDestination(int maxByteCount) { 52 | super(maxByteCount); 53 | } 54 | 55 | @Override 56 | public ByteBuffer getByteBuffer() { 57 | int concurrentAccessCount = concurrentAccessCounter.incrementAndGet(); 58 | if (concurrentAccessCount > 1) { 59 | throw new ConcurrentAccessError(concurrentAccessCount); 60 | } 61 | try { 62 | return super.getByteBuffer(); 63 | } finally { 64 | concurrentAccessCounter.decrementAndGet(); 65 | } 66 | } 67 | 68 | @Override 69 | public ByteBuffer drain(ByteBuffer byteBuffer) { 70 | int concurrentAccessCount = concurrentAccessCounter.incrementAndGet(); 71 | if (concurrentAccessCount > 1) { 72 | throw new ConcurrentAccessError(concurrentAccessCount); 73 | } 74 | try { 75 | return super.drain(byteBuffer); 76 | } finally { 77 | concurrentAccessCounter.decrementAndGet(); 78 | } 79 | } 80 | 81 | @Override 82 | public void writeBytes(ByteBuffer byteBuffer) { 83 | int concurrentAccessCount = concurrentAccessCounter.incrementAndGet(); 84 | if (concurrentAccessCount > 1) { 85 | throw new ConcurrentAccessError(concurrentAccessCount); 86 | } 87 | try { 88 | super.writeBytes(byteBuffer); 89 | } finally { 90 | concurrentAccessCounter.decrementAndGet(); 91 | } 92 | } 93 | 94 | @Override 95 | public void writeBytes(byte[] data, int offset, int length) { 96 | int concurrentAccessCount = concurrentAccessCounter.incrementAndGet(); 97 | if (concurrentAccessCount > 1) { 98 | throw new ConcurrentAccessError(concurrentAccessCount); 99 | } 100 | try { 101 | super.writeBytes(data, offset, length); 102 | } finally { 103 | concurrentAccessCounter.decrementAndGet(); 104 | } 105 | } 106 | 107 | } 108 | 109 | private static final LogEvent[] LOG_EVENTS = createMessages(); 110 | 111 | private static LogEvent[] createMessages() { 112 | int messageCount = 1_000; 113 | int minMessageLength = 1; 114 | int maxMessageLength = 1_000; 115 | Random random = new Random(0); 116 | return IntStream 117 | .range(0, messageCount) 118 | .mapToObj(ignored -> { 119 | int messageLength = minMessageLength + random.nextInt(maxMessageLength); 120 | int startIndex = random.nextInt(10); 121 | String messageText = IntStream 122 | .range(0, messageLength) 123 | .mapToObj(charIndex -> { 124 | int digit = (startIndex + charIndex) % 10; 125 | return String.valueOf(digit); 126 | }) 127 | .collect(Collectors.joining("")); 128 | SimpleMessage message = new SimpleMessage(messageText); 129 | return Log4jLogEvent 130 | .newBuilder() 131 | .setMessage(message) 132 | .build(); 133 | }) 134 | .toArray(LogEvent[]::new); 135 | } 136 | 137 | @Test 138 | public void test_concurrent_encode() { 139 | int threadCount = 10; 140 | int maxAppendCount = 1_000; 141 | AtomicReference encodeFailureRef = new AtomicReference<>(null); 142 | produce(threadCount, maxAppendCount, encodeFailureRef); 143 | Assertions.assertThat(encodeFailureRef.get()).isNull(); 144 | } 145 | 146 | private void produce( 147 | int threadCount, 148 | int maxEncodeCount, 149 | AtomicReference encodeFailureRef) { 150 | int maxByteCount = LogstashLayout.newBuilder().getMaxByteCount(); 151 | LogstashLayout layout = createLayout(maxByteCount); 152 | ByteBufferDestination destination = new ConcurrentAccessDetectingByteBufferDestination(maxByteCount); 153 | AtomicLong encodeCounter = new AtomicLong(0); 154 | List workers = IntStream 155 | .range(0, threadCount) 156 | .mapToObj(threadIndex -> createWorker(layout, destination, encodeFailureRef, maxEncodeCount, encodeCounter, threadIndex)) 157 | .collect(Collectors.toList()); 158 | workers.forEach(Thread::start); 159 | workers.forEach(worker -> { 160 | try { 161 | worker.join(); 162 | } catch (InterruptedException ignored) { 163 | System.err.format("join to %s interrupted%n", worker.getName()); 164 | } 165 | }); 166 | } 167 | 168 | private LogstashLayout createLayout(int maxByteCount) { 169 | Configuration configuration = new DefaultConfiguration(); 170 | return LogstashLayout 171 | .newBuilder() 172 | .setConfiguration(configuration) 173 | .setMaxByteCount(maxByteCount) 174 | .setEventTemplate("{\"message\": \"${json:message}\"}") 175 | .setStackTraceEnabled(false) 176 | .setLocationInfoEnabled(false) 177 | .setPrettyPrintEnabled(false) 178 | .build(); 179 | } 180 | 181 | private Thread createWorker( 182 | LogstashLayout layout, 183 | ByteBufferDestination destination, 184 | AtomicReference encodeFailureRef, 185 | int maxEncodeCount, 186 | AtomicLong encodeCounter, 187 | int threadIndex) { 188 | String threadName = String.format("Worker-%d", threadIndex); 189 | return new Thread( 190 | () -> { 191 | try { 192 | for (int logEventIndex = threadIndex % LOG_EVENTS.length; 193 | encodeFailureRef.get() == null && encodeCounter.incrementAndGet() < maxEncodeCount; 194 | logEventIndex = (logEventIndex + 1) % LOG_EVENTS.length) { 195 | LogEvent logEvent = LOG_EVENTS[logEventIndex]; 196 | layout.encode(logEvent, destination); 197 | } 198 | } catch (Exception error) { 199 | boolean succeeded = encodeFailureRef.compareAndSet(null, error); 200 | if (succeeded) { 201 | System.err.format("%s failed%n", threadName); 202 | error.printStackTrace(System.err); 203 | } 204 | } 205 | }, 206 | threadName); 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /layout/src/test/java/com/vlkan/log4j2/logstash/layout/LogstashLayoutJsonGeneratorStateResetTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | import com.fasterxml.jackson.core.JsonFactory; 20 | import com.fasterxml.jackson.core.JsonGenerator; 21 | import com.fasterxml.jackson.core.JsonParser; 22 | import com.fasterxml.jackson.core.JsonStreamContext; 23 | import org.apache.commons.lang3.RandomUtils; 24 | import org.apache.logging.log4j.core.LogEvent; 25 | import org.apache.logging.log4j.core.config.Configuration; 26 | import org.apache.logging.log4j.core.config.DefaultConfiguration; 27 | import org.apache.logging.log4j.core.impl.Log4jLogEvent; 28 | import org.apache.logging.log4j.message.SimpleMessage; 29 | import org.assertj.core.api.Assertions; 30 | import org.junit.Test; 31 | 32 | import java.nio.Buffer; 33 | import java.nio.BufferOverflowException; 34 | import java.nio.ByteBuffer; 35 | import java.nio.charset.StandardCharsets; 36 | 37 | import static com.vlkan.log4j2.logstash.layout.ObjectMapperFixture.OBJECT_MAPPER; 38 | 39 | public class LogstashLayoutJsonGeneratorStateResetTest { 40 | 41 | private static final Configuration CONFIGURATION = new DefaultConfiguration(); 42 | 43 | private static final LogstashLayout LAYOUT = LogstashLayout 44 | .newBuilder() 45 | .setConfiguration(CONFIGURATION) 46 | .setEventTemplate("{\"message\": \"${json:message}\"}") 47 | .setStackTraceEnabled(true) 48 | .setLocationInfoEnabled(true) 49 | .setEmptyPropertyExclusionEnabled(true) 50 | .build(); 51 | 52 | private static final JsonFactory JSON_FACTORY = OBJECT_MAPPER.getFactory(); 53 | 54 | private static final int MAX_BYTE_COUNT = LogstashLayout.newBuilder().getMaxByteCount(); 55 | 56 | private static final LogEvent HUGE_LOG_EVENT = createLogEventExceedingMaxByteCount(); 57 | 58 | private static final LogEvent LITE_LOG_EVENT = LogEventFixture.createLiteLogEvents(1).get(0); 59 | 60 | private static LogEvent createLogEventExceedingMaxByteCount() { 61 | String messageText = new String(RandomUtils.nextBytes(MAX_BYTE_COUNT)); 62 | SimpleMessage message = new SimpleMessage(messageText); 63 | return Log4jLogEvent 64 | .newBuilder() 65 | .setMessage(message) 66 | .build(); 67 | } 68 | 69 | @Test 70 | public void test_toByteArray() throws Exception { 71 | test_serializer_recover_after_buffer_overflow(LAYOUT::toByteArray); 72 | } 73 | 74 | @Test 75 | public void test_toSerializable() throws Exception { 76 | test_serializer_recover_after_buffer_overflow(logEvent-> { 77 | String serializableLogEvent = LAYOUT.toSerializable(logEvent); 78 | return serializableLogEvent.getBytes(StandardCharsets.UTF_8); 79 | }); 80 | } 81 | 82 | @Test 83 | public void test_encode() throws Exception { 84 | FixedByteBufferDestination destination = new FixedByteBufferDestination(MAX_BYTE_COUNT); 85 | test_serializer_recover_after_buffer_overflow(logEvent-> { 86 | LAYOUT.encode(logEvent, destination); 87 | return copyWrittenBytes(destination.getByteBuffer()); 88 | }); 89 | } 90 | 91 | private byte[] copyWrittenBytes(ByteBuffer byteBuffer) { 92 | Buffer flip = byteBuffer.flip(); 93 | byte[] writtenBytes = new byte[flip.remaining()]; 94 | byteBuffer.get(writtenBytes); 95 | return writtenBytes; 96 | } 97 | 98 | private void test_serializer_recover_after_buffer_overflow(ThrowingFunction serializer) throws Exception { 99 | Assertions 100 | .assertThatThrownBy(() -> serializer.apply(HUGE_LOG_EVENT)) 101 | .hasCauseInstanceOf(BufferOverflowException.class); 102 | test_JsonGenerator_state_reset(); 103 | test_jsonBytes(serializer.apply(LITE_LOG_EVENT)); 104 | } 105 | 106 | private void test_jsonBytes(byte[] jsonBytes) { 107 | String json = new String(jsonBytes, StandardCharsets.UTF_8); 108 | Assertions 109 | .assertThatCode(() -> { 110 | JsonParser parser = JSON_FACTORY.createParser(json); 111 | // noinspection StatementWithEmptyBody (consume each token) 112 | while (parser.nextToken() != null) ; 113 | }) 114 | .as("should be a valid JSON: %s", json) 115 | .doesNotThrowAnyException(); 116 | } 117 | 118 | private void test_JsonGenerator_state_reset() { 119 | LogstashLayoutSerializationContext serializationContext = LAYOUT.getSerializationContext(); 120 | ByteBuffer byteBuffer = serializationContext.getOutputStream().getByteBuffer(); 121 | JsonGenerator jsonGenerator = serializationContext.getJsonGenerator(); 122 | JsonStreamContext outputContext = jsonGenerator.getOutputContext(); 123 | Assertions.assertThat(outputContext.inRoot()).isTrue(); 124 | Assertions.assertThat(outputContext.inObject()).isFalse(); 125 | Assertions.assertThat(outputContext.inArray()).isFalse(); 126 | //noinspection RedundantCast (for Java 8 compatibility) 127 | Assertions.assertThat(((Buffer) byteBuffer).position()).isEqualTo(0); 128 | } 129 | 130 | private interface ThrowingFunction { 131 | 132 | O apply(I input) throws Exception; 133 | 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /layout/src/test/java/com/vlkan/log4j2/logstash/layout/ObjectMapperFixture.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | 21 | public enum ObjectMapperFixture {; 22 | 23 | public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /layout/src/test/java/com/vlkan/log4j2/logstash/layout/util/JsonGeneratorsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout.util; 18 | 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import com.vlkan.log4j2.logstash.layout.ObjectMapperFixture; 21 | import org.assertj.core.api.Assertions; 22 | import org.junit.Test; 23 | 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.IOException; 26 | import java.nio.charset.StandardCharsets; 27 | import java.util.LinkedHashMap; 28 | import java.util.Map; 29 | 30 | public class JsonGeneratorsTest { 31 | 32 | private static final class WriteDoubleTestCase { 33 | 34 | private final long integralPart; 35 | 36 | private final int fractionalPart; 37 | 38 | private WriteDoubleTestCase(long integralPart, int fractionalPart) { 39 | this.integralPart = integralPart; 40 | this.fractionalPart = fractionalPart; 41 | } 42 | 43 | private String write() throws IOException { 44 | try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 45 | JsonGenerator jsonGenerator = ObjectMapperFixture 46 | .OBJECT_MAPPER 47 | .getFactory() 48 | .createGenerator(outputStream)) { 49 | JsonGenerators.writeDouble(jsonGenerator, integralPart, fractionalPart); 50 | jsonGenerator.flush(); 51 | return outputStream.toString(StandardCharsets.UTF_8.name()); 52 | } 53 | } 54 | 55 | } 56 | 57 | @Test 58 | public void test_writeDouble() { 59 | 60 | // Create test cases. 61 | Map testCaseByExpectedJson = new LinkedHashMap<>(); 62 | testCaseByExpectedJson.put( 63 | "" + Long.MIN_VALUE, 64 | new WriteDoubleTestCase(Long.MIN_VALUE, 0)); 65 | testCaseByExpectedJson.put( 66 | "" + Long.MIN_VALUE + '.' + Integer.MAX_VALUE, 67 | new WriteDoubleTestCase(Long.MIN_VALUE, Integer.MAX_VALUE)); 68 | testCaseByExpectedJson.put( 69 | "" + Long.MAX_VALUE, 70 | new WriteDoubleTestCase(Long.MAX_VALUE, 0)); 71 | testCaseByExpectedJson.put( 72 | "" + Long.MAX_VALUE + '.' + Integer.MAX_VALUE, 73 | new WriteDoubleTestCase(Long.MAX_VALUE, Integer.MAX_VALUE)); 74 | testCaseByExpectedJson.put("0", new WriteDoubleTestCase(0, 0)); 75 | testCaseByExpectedJson.put("1", new WriteDoubleTestCase(1, 0)); 76 | testCaseByExpectedJson.put("-1", new WriteDoubleTestCase(-1, 0)); 77 | testCaseByExpectedJson.put("1.2", new WriteDoubleTestCase(1, 2)); 78 | testCaseByExpectedJson.put("-1.2", new WriteDoubleTestCase(-1, 2)); 79 | 80 | // Execute test cases. 81 | testCaseByExpectedJson.forEach((expectedJson, testCase) -> { 82 | try { 83 | String actualJson = testCase.write(); 84 | Assertions 85 | .assertThat(actualJson) 86 | .as( 87 | "integralPart=%d, fractionalPart=%d", 88 | testCase.integralPart, testCase.fractionalPart) 89 | .isEqualTo(expectedJson); 90 | } catch (IOException error) { 91 | throw new RuntimeException(error); 92 | } 93 | }); 94 | 95 | } 96 | 97 | @Test 98 | public void test_writeDouble_with_negative_fractionalPart() { 99 | Assertions 100 | .assertThatThrownBy(() -> new WriteDoubleTestCase(0, -1).write()) 101 | .isInstanceOf(IllegalArgumentException.class) 102 | .hasMessageContaining("negative fraction"); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /layout/src/test/perf/com/vlkan/log4j2/logstash/layout/BlackHoleByteBufferDestination.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 Volkan Yazıcı 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permits and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.vlkan.log4j2.logstash.layout; 18 | 19 | import org.apache.logging.log4j.core.layout.ByteBufferDestination; 20 | 21 | import java.nio.ByteBuffer; 22 | 23 | class BlackHoleByteBufferDestination implements ByteBufferDestination { 24 | 25 | private final ByteBuffer byteBuffer; 26 | 27 | BlackHoleByteBufferDestination(int maxByteCount) { 28 | this.byteBuffer = ByteBuffer.allocate(maxByteCount); 29 | } 30 | 31 | @Override 32 | public ByteBuffer getByteBuffer() { 33 | return byteBuffer; 34 | } 35 | 36 | @Override 37 | public ByteBuffer drain(ByteBuffer byteBuffer) { 38 | byteBuffer.clear(); 39 | return byteBuffer; 40 | } 41 | 42 | @Override 43 | public void writeBytes(ByteBuffer byteBuffer) { 44 | byteBuffer.clear(); 45 | } 46 | 47 | @Override 48 | public void writeBytes(byte[] data, int offset, int length) {} 49 | 50 | } 51 | -------------------------------------------------------------------------------- /layout/src/test/perf/com/vlkan/log4j2/logstash/layout/LogstashLayoutBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.vlkan.log4j2.logstash.layout; 2 | 3 | import org.apache.logging.log4j.core.Layout; 4 | import org.apache.logging.log4j.core.LogEvent; 5 | import org.apache.logging.log4j.core.layout.ByteBufferDestination; 6 | import org.openjdk.jmh.annotations.Benchmark; 7 | import org.openjdk.jmh.profile.GCProfiler; 8 | import org.openjdk.jmh.results.format.ResultFormatType; 9 | import org.openjdk.jmh.runner.Runner; 10 | import org.openjdk.jmh.runner.options.ChainedOptionsBuilder; 11 | import org.openjdk.jmh.runner.options.Options; 12 | import org.openjdk.jmh.runner.options.OptionsBuilder; 13 | import org.openjdk.jmh.runner.options.TimeValue; 14 | 15 | import java.io.File; 16 | import java.net.URL; 17 | import java.net.URLClassLoader; 18 | import java.nio.ByteBuffer; 19 | import java.util.List; 20 | 21 | public class LogstashLayoutBenchmark { 22 | 23 | public static void main(String[] args) throws Exception { 24 | fixJavaClassPath(); 25 | ChainedOptionsBuilder optionsBuilder = new OptionsBuilder() 26 | .include(LogstashLayoutBenchmark.class.getSimpleName()) 27 | .forks(2) 28 | .warmupIterations(3) 29 | .warmupTime(TimeValue.seconds(20)) 30 | .measurementIterations(3) 31 | .measurementTime(TimeValue.seconds(30)) 32 | .addProfiler(GCProfiler.class); 33 | enableJsonOutput(optionsBuilder); 34 | Options options = optionsBuilder 35 | .build(); 36 | new Runner(options).run(); 37 | } 38 | 39 | /** 40 | * Add project dependencies to java.class.path property used by JMH. 41 | * @see How to Run a JMH Benchmark in Maven Using exec:java Instead of exec:exec 42 | */ 43 | private static void fixJavaClassPath() { 44 | URLClassLoader classLoader = (URLClassLoader) LogstashLayoutBenchmark.class.getClassLoader(); 45 | StringBuilder classpathBuilder = new StringBuilder(); 46 | for (URL url : classLoader.getURLs()) { 47 | String urlPath = url.getPath(); 48 | classpathBuilder.append(urlPath).append(File.pathSeparator); 49 | } 50 | String classpath = classpathBuilder.toString(); 51 | System.setProperty("java.class.path", classpath); 52 | } 53 | 54 | private static void enableJsonOutput(ChainedOptionsBuilder optionsBuilder) { 55 | String jsonOutputFile = System.getProperty("log4j2.logstashLayoutBenchmark.jsonOutputFile"); 56 | if (jsonOutputFile != null) { 57 | optionsBuilder 58 | .resultFormat(ResultFormatType.JSON) 59 | .result(jsonOutputFile); 60 | } 61 | } 62 | 63 | @Benchmark 64 | public static int fullLogstashLayout4JsonLayout(LogstashLayoutBenchmarkState state) { 65 | return benchmark(state.getLogstashLayout4JsonLayout(), state.getFullLogEvents(), state.getByteBufferDestination()); 66 | } 67 | 68 | @Benchmark 69 | public static int liteLogstashLayout4JsonLayout(LogstashLayoutBenchmarkState state) { 70 | return benchmark(state.getLogstashLayout4JsonLayout(), state.getLiteLogEvents(), state.getByteBufferDestination()); 71 | } 72 | 73 | @Benchmark 74 | public static int fullLogstashLayout4EcsLayout(LogstashLayoutBenchmarkState state) { 75 | return benchmark(state.getLogstashLayout4EcsLayout(), state.getFullLogEvents(), state.getByteBufferDestination()); 76 | } 77 | 78 | @Benchmark 79 | public static int liteLogstashLayout4EcsLayout(LogstashLayoutBenchmarkState state) { 80 | return benchmark(state.getLogstashLayout4EcsLayout(), state.getLiteLogEvents(), state.getByteBufferDestination()); 81 | } 82 | 83 | @Benchmark 84 | public static int fullLogstashLayout4GelfLayout(LogstashLayoutBenchmarkState state) { 85 | return benchmark(state.getLogstashLayout4GelfLayout(), state.getFullLogEvents(), state.getByteBufferDestination()); 86 | } 87 | 88 | @Benchmark 89 | public static int liteLogstashLayout4GelfLayout(LogstashLayoutBenchmarkState state) { 90 | return benchmark(state.getLogstashLayout4GelfLayout(), state.getLiteLogEvents(), state.getByteBufferDestination()); 91 | } 92 | 93 | @Benchmark 94 | public static int fullDefaultJsonLayout(LogstashLayoutBenchmarkState state) { 95 | return benchmark(state.getDefaultJsonLayout(), state.getFullLogEvents(), state.getByteBufferDestination()); 96 | } 97 | 98 | @Benchmark 99 | public static int liteDefaultJsonLayout(LogstashLayoutBenchmarkState state) { 100 | return benchmark(state.getDefaultJsonLayout(), state.getLiteLogEvents(), state.getByteBufferDestination()); 101 | } 102 | 103 | @Benchmark 104 | public static int fullCustomJsonLayout(LogstashLayoutBenchmarkState state) { 105 | return benchmark(state.getCustomJsonLayout(), state.getFullLogEvents(), state.getByteBufferDestination()); 106 | } 107 | 108 | @Benchmark 109 | public static int liteCustomJsonLayout(LogstashLayoutBenchmarkState state) { 110 | return benchmark(state.getCustomJsonLayout(), state.getLiteLogEvents(), state.getByteBufferDestination()); 111 | } 112 | 113 | @Benchmark 114 | public static int fullEcsLayout(LogstashLayoutBenchmarkState state) { 115 | return benchmark(state.getEcsLayout(), state.getFullLogEvents(), state.getByteBufferDestination()); 116 | } 117 | 118 | @Benchmark 119 | public static int liteEcsLayout(LogstashLayoutBenchmarkState state) { 120 | return benchmark(state.getEcsLayout(), state.getLiteLogEvents(), state.getByteBufferDestination()); 121 | } 122 | 123 | @Benchmark 124 | public static int fullGelfLayout(LogstashLayoutBenchmarkState state) { 125 | return benchmark(state.getGelfLayout(), state.getFullLogEvents(), state.getByteBufferDestination()); 126 | } 127 | 128 | @Benchmark 129 | public static int liteGelfLayout(LogstashLayoutBenchmarkState state) { 130 | return benchmark(state.getGelfLayout(), state.getLiteLogEvents(), state.getByteBufferDestination()); 131 | } 132 | 133 | private static int benchmark(Layout layout, List logEvents, ByteBufferDestination destination) { 134 | // noinspection ForLoopReplaceableByForEach (for loop avoids iterator allocations) 135 | for (int logEventIndex = 0; logEventIndex < logEvents.size(); logEventIndex++) { 136 | LogEvent logEvent = logEvents.get(logEventIndex); 137 | layout.encode(logEvent, destination); 138 | } 139 | ByteBuffer byteBuffer = destination.getByteBuffer(); 140 | int position = byteBuffer.position(); 141 | byteBuffer.clear(); 142 | return position; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /layout/src/test/perf/com/vlkan/log4j2/logstash/layout/LogstashLayoutBenchmarkState.java: -------------------------------------------------------------------------------- 1 | package com.vlkan.log4j2.logstash.layout; 2 | 3 | import co.elastic.logging.log4j2.EcsLayout; 4 | import org.apache.logging.log4j.core.LogEvent; 5 | import org.apache.logging.log4j.core.config.Configuration; 6 | import org.apache.logging.log4j.core.config.DefaultConfiguration; 7 | import org.apache.logging.log4j.core.layout.ByteBufferDestination; 8 | import org.apache.logging.log4j.core.layout.GelfLayout; 9 | import org.apache.logging.log4j.core.layout.JsonLayout; 10 | import org.apache.logging.log4j.core.util.KeyValuePair; 11 | import org.apache.logging.log4j.core.util.NetUtils; 12 | import org.openjdk.jmh.annotations.Scope; 13 | import org.openjdk.jmh.annotations.State; 14 | 15 | import java.util.List; 16 | 17 | @State(Scope.Benchmark) 18 | public class LogstashLayoutBenchmarkState { 19 | 20 | private static final Configuration CONFIGURATION = new DefaultConfiguration(); 21 | 22 | private static final int LOGSTASH_LAYOUT_MAX_BYTE_COUNT = 4096; 23 | 24 | private final ByteBufferDestination byteBufferDestination; 25 | 26 | private final LogstashLayout logstashLayout4JsonLayout; 27 | 28 | private final LogstashLayout logstashLayout4EcsLayout; 29 | 30 | private final LogstashLayout logstashLayout4GelfLayout; 31 | 32 | private final JsonLayout defaultJsonLayout; 33 | 34 | private final JsonLayout customJsonLayout; 35 | 36 | private final EcsLayout ecsLayout; 37 | 38 | private final GelfLayout gelfLayout; 39 | 40 | private final List fullLogEvents; 41 | 42 | private final List liteLogEvents; 43 | 44 | public LogstashLayoutBenchmarkState() { 45 | this.byteBufferDestination = new BlackHoleByteBufferDestination(1024 * 512); 46 | this.logstashLayout4JsonLayout = createLogstashLayout4JsonLayout(); 47 | this.logstashLayout4EcsLayout = createLogstashLayout4EcsLayout(); 48 | this.logstashLayout4GelfLayout = createLogstashLayout4GelfLayout(); 49 | this.defaultJsonLayout = createDefaultJsonLayout(); 50 | this.customJsonLayout = createCustomJsonLayout(); 51 | this.ecsLayout = createEcsLayout(); 52 | this.gelfLayout = createGelfLayout(); 53 | int logEventCount = 1_000; 54 | this.fullLogEvents = LogEventFixture.createFullLogEvents(logEventCount); 55 | this.liteLogEvents = LogEventFixture.createLiteLogEvents(logEventCount); 56 | } 57 | 58 | private static LogstashLayout createLogstashLayout4JsonLayout() { 59 | return LogstashLayout 60 | .newBuilder() 61 | .setConfiguration(CONFIGURATION) 62 | .setEventTemplateUri("classpath:Log4j2JsonLayout.json") 63 | .setStackTraceEnabled(true) 64 | .setMaxByteCount(LOGSTASH_LAYOUT_MAX_BYTE_COUNT) 65 | .build(); 66 | } 67 | 68 | private static LogstashLayout createLogstashLayout4EcsLayout() { 69 | LogstashLayout.EventTemplateAdditionalFields additionalFields = LogstashLayout 70 | .EventTemplateAdditionalFields 71 | .newBuilder() 72 | .setPairs(new KeyValuePair[]{new KeyValuePair("service.name", "benchmark")}) 73 | .build(); 74 | return LogstashLayout 75 | .newBuilder() 76 | .setConfiguration(CONFIGURATION) 77 | .setEventTemplateUri("classpath:EcsLayout.json") 78 | .setStackTraceEnabled(true) 79 | .setEventTemplateAdditionalFields(additionalFields) 80 | .setMaxByteCount(LOGSTASH_LAYOUT_MAX_BYTE_COUNT) 81 | .build(); 82 | } 83 | 84 | private static LogstashLayout createLogstashLayout4GelfLayout() { 85 | return LogstashLayout 86 | .newBuilder() 87 | .setConfiguration(CONFIGURATION) 88 | .setEventTemplateUri("classpath:GelfLayout.json") 89 | .setStackTraceEnabled(true) 90 | .setMaxByteCount(LOGSTASH_LAYOUT_MAX_BYTE_COUNT) 91 | .setEventTemplateAdditionalFields(LogstashLayout 92 | .EventTemplateAdditionalFields 93 | .newBuilder() 94 | .setPairs(new KeyValuePair[]{ 95 | // Adding "host" as a constant rather than using 96 | // the "hostName" property lookup at runtime, which 97 | // is what GelfLayout does as well. 98 | new KeyValuePair("host", NetUtils.getLocalHostname()) 99 | }) 100 | .build()) 101 | .build(); 102 | } 103 | 104 | private static JsonLayout createDefaultJsonLayout() { 105 | @SuppressWarnings("unchecked") JsonLayout.Builder builder = JsonLayout.newBuilder(); 106 | builder.setConfiguration(CONFIGURATION); 107 | return builder.build(); 108 | } 109 | 110 | private static JsonLayout createCustomJsonLayout() { 111 | @SuppressWarnings("unchecked") JsonLayout.Builder builder = JsonLayout.newBuilder(); 112 | builder.setConfiguration(CONFIGURATION); 113 | builder.setAdditionalFields(new KeyValuePair[]{new KeyValuePair("@version", "1")}); 114 | return builder.build(); 115 | } 116 | 117 | private static EcsLayout createEcsLayout() { 118 | return EcsLayout 119 | .newBuilder() 120 | .setConfiguration(CONFIGURATION) 121 | .setServiceName("benchmark") 122 | .build(); 123 | } 124 | 125 | private static GelfLayout createGelfLayout() { 126 | return GelfLayout 127 | .newBuilder() 128 | .setConfiguration(CONFIGURATION) 129 | .setCompressionType(GelfLayout.CompressionType.OFF) 130 | .build(); 131 | } 132 | 133 | ByteBufferDestination getByteBufferDestination() { 134 | return byteBufferDestination; 135 | } 136 | 137 | LogstashLayout getLogstashLayout4JsonLayout() { 138 | return logstashLayout4JsonLayout; 139 | } 140 | 141 | LogstashLayout getLogstashLayout4EcsLayout() { 142 | return logstashLayout4EcsLayout; 143 | } 144 | 145 | LogstashLayout getLogstashLayout4GelfLayout() { 146 | return logstashLayout4GelfLayout; 147 | } 148 | 149 | JsonLayout getDefaultJsonLayout() { 150 | return defaultJsonLayout; 151 | } 152 | 153 | JsonLayout getCustomJsonLayout() { 154 | return customJsonLayout; 155 | } 156 | 157 | EcsLayout getEcsLayout() { 158 | return ecsLayout; 159 | } 160 | 161 | GelfLayout getGelfLayout() { 162 | return gelfLayout; 163 | } 164 | 165 | List getFullLogEvents() { 166 | return fullLogEvents; 167 | } 168 | 169 | List getLiteLogEvents() { 170 | return liteLogEvents; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /layout/src/test/resources/Log4j2JsonLayout.json: -------------------------------------------------------------------------------- 1 | { 2 | "instant": { 3 | "epochSecond": "${json:timestamp:epoch:divisor=1e9,integral}", 4 | "nanoOfSecond": 0 5 | }, 6 | "thread": "${json:thread:name}", 7 | "level": "${json:level}", 8 | "loggerName": "${json:logger:name}", 9 | "message": "${json:message}", 10 | "thrown": { 11 | "message": "${json:exception:message}", 12 | "name": "${json:exception:className}", 13 | "extendedStackTrace": "${json:exception:stackTrace}" 14 | }, 15 | "contextStack": "${json:ndc}", 16 | "endOfBatch": "${json:endOfBatch}", 17 | "loggerFqcn": "${json:logger:fqcn}", 18 | "contextMap": "${json:mdc}", 19 | "threadId": "${json:thread:id}", 20 | "threadPriority": "${json:thread:priority}", 21 | "source": { 22 | "class": "${json:source:className}", 23 | "method": "${json:source:methodName}", 24 | "file": "${json:source:fileName}", 25 | "line": "${json:source:lineNumber}" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /layout/src/test/resources/LogstashTestLayout.json: -------------------------------------------------------------------------------- 1 | { 2 | "mdc": "${json:mdc}", 3 | "ndc": "${json:ndc}", 4 | "exception_class": "${json:exception:className}", 5 | "exception_message": "${json:exception:message}", 6 | "stacktrace": "${json:exception:stackTrace:text}", 7 | "line_number": "${json:source:lineNumber}", 8 | "class": "${json:source:className}", 9 | "@version": 1, 10 | "source_host": "${hostName}", 11 | "message": "${json:message}", 12 | "thread_id": "${json:thread:id}", 13 | "thread_name": "${json:thread:name}", 14 | "thread_priority": "${json:thread:priority}", 15 | "@timestamp": "${json:timestamp}", 16 | "level": "${json:level}", 17 | "file": "${json:source:fileName}", 18 | "method": "${json:source:methodName}", 19 | "logger_fqcn": "${json:logger:fqcn}", 20 | "logger_name": "${json:logger:name}", 21 | "end_of_batch": "${json:endOfBatch}", 22 | "lookup_test_key": "${sys:lookup_test_key}" 23 | } 24 | -------------------------------------------------------------------------------- /maven-version-rules.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | .*-(alpha|beta)[0-9\.-]* 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | --------------------------------------------------------------------------------