├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── settings.gradle └── src ├── main └── java │ └── com │ └── nixsolutions │ └── logging │ ├── LogContext.java │ ├── LogContextDefault.java │ ├── LogContextJson.java │ ├── LoggingConstants.java │ ├── ObjectlessPrettyLoggable.java │ ├── PrettyLoggable.java │ ├── advice │ ├── LoggingAdvice.java │ └── handler │ │ ├── LogActionHandler.java │ │ ├── LogActionHandlerFactory.java │ │ ├── LogEntryActionHandler.java │ │ ├── LogExectimeActionHandler.java │ │ ├── LogExitActionHandler.java │ │ └── base │ │ ├── AbstractLogActionHandler.java │ │ └── LogFlowActionHandler.java │ ├── annotation │ ├── ContextParam.java │ ├── Log.java │ └── LoggableType.java │ ├── common │ ├── MapUtils.java │ └── WordUtils.java │ ├── configuration │ ├── ContextExtractorFactoryConfiguration.java │ └── LoggingConfiguration.java │ └── parameters │ ├── extractor │ ├── ContextParamExtractor.java │ ├── ContextParamExtractorFactory.java │ └── impl │ │ ├── LongContextParamExtractor.java │ │ └── StringContextParamExtractor.java │ └── loggabletype │ ├── AnnotatedObject.java │ ├── ContextParamsAccessor.java │ ├── ExtractionResolutionStrategy.java │ ├── LookupResult.java │ ├── exception │ ├── LookupConflictException.java │ ├── RecursiveLookupException.java │ ├── RepeatedFieldsException.java │ └── UnresolvedLookupException.java │ └── util │ ├── AnnotatedTypeReflectionUtils.java │ ├── AnnotationLookupConstants.java │ ├── AnnotationReflectionLookupUtils.java │ └── LookupUtils.java └── test ├── java └── com │ └── nixsolutions │ └── logging │ ├── LogContextDefaultTest.java │ ├── advice │ ├── LoggingAdviceTest.java │ ├── LoggingResultHelper.java │ └── pojo │ │ ├── EnumType.java │ │ └── Pojo.java │ ├── integration │ └── SampleService.java │ └── parameters │ └── loggabletype │ ├── AnnotationReflectionLookupUtilsTest.java │ ├── LookupResultTest.java │ └── cases │ ├── BasePojo.java │ ├── accessorinterface │ └── AccessorInterfacePojo.java │ ├── annotatedmethod │ └── AnnotatedMethodPojo.java │ ├── annotatedmethodfails │ └── AnnotatedMEthodFailsPojo.java │ ├── caseinheritance │ ├── ChildPojo.java │ ├── GrandParentPojo.java │ └── ParentPojo.java │ ├── conflictinglookup │ ├── ConflictingLookupPojo.java │ └── ConflictingLookupPojoExtractor.java │ ├── donothinglookup │ ├── DoNothingLookupPojo.java │ └── DoNothingLookupPojoExtractor.java │ ├── emptypojo │ └── Empty.java │ ├── enumtypefield │ ├── EnumType.java │ └── PojoWithEnumField.java │ ├── multipleannotatedmethods │ └── MultipleAnnotatedMethodsPojo.java │ ├── nestedcollector │ ├── EvenMoreNestedPojo.java │ ├── NestedPojo.java │ └── Pojo.java │ ├── nestedextractor │ ├── NestedPojo.java │ ├── NestedPojoExtractor.java │ └── PojoWithNestedPojo.java │ ├── notannotatedfields │ └── NotAnnotatedFieldsPojo.java │ ├── recursivefail │ ├── RecursiveLoopPojo1.java │ └── RecursiveLoopPojo2.java │ ├── renamedcomplexfield │ ├── ComplexFieldRenamedPojo.java │ └── ComplexPojo.java │ ├── renamedfield │ └── RenamedFieldPojo.java │ ├── repeatedfieldnames │ ├── RepatedFieldsParentPojo.java │ └── RepeatedFieldnamesPojo.java │ ├── simpleextractor │ ├── SimpleExtractorPojo.java │ └── SimpleExtractorPojoExtractor.java │ └── unextractablefield │ ├── PojoWithUnextractableField.java │ └── UnextractablePojo.java └── resources ├── com └── nixsolutions │ └── logging │ └── advice │ ├── entry │ ├── complexParam.json │ ├── emptyCtx.json │ ├── multipleParams.json │ ├── renamedParam.json │ └── strParam.json │ ├── exectime │ ├── adjustedTaskName.json │ ├── changedTimeUnit.json │ ├── humanReadableTaskName.json │ └── simpleTimeLogging.json │ └── exit │ ├── pojoReturnParam.json │ ├── strReturnParam.json │ └── voidReturn.json └── logback.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Intellij ### 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 5 | 6 | *.iml 7 | 8 | ## Directory-based project format: 9 | .idea/ 10 | # if you remove the above rule, at least ignore the following: 11 | 12 | # User-specific stuff: 13 | # .idea/workspace.xml 14 | # .idea/tasks.xml 15 | # .idea/dictionaries 16 | 17 | # Sensitive or high-churn files: 18 | # .idea/dataSources.ids 19 | # .idea/dataSources.xml 20 | # .idea/sqlDataSources.xml 21 | # .idea/dynamic.xml 22 | # .idea/uiDesigner.xml 23 | 24 | # Gradle: 25 | # .idea/gradle.xml 26 | # .idea/libraries 27 | 28 | # Mongo Explorer plugin: 29 | # .idea/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.ipr 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Crashlytics plugin (for Android Studio and IntelliJ) 47 | com_crashlytics_export_strings.xml 48 | crashlytics.properties 49 | crashlytics-build.properties 50 | 51 | 52 | ### Gradle ### 53 | .gradle 54 | build/ 55 | 56 | # Ignore Gradle GUI config 57 | gradle-app.setting 58 | 59 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 60 | !gradle-wrapper.jar 61 | 62 | 63 | ### Java ### 64 | *.class 65 | 66 | # Mobile Tools for Java (J2ME) 67 | .mtj.tmp/ 68 | 69 | # Package Files # 70 | *.jar 71 | *.war 72 | *.ear 73 | 74 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 75 | hs_err_pid* 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 NIX Solutions Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Log Structured Data 4 Java (lsd4j) 2 | 3 | ### Overview ### 4 | 5 | This approach was created to make application logging unified and produce sensible logs in a single format that is recognizable by log parsing software (LogStash, etc). 6 | 7 | ### Requirements ### 8 | 9 | Java >= 1.8 10 | 11 | ### Installation ### 12 | 13 | Maven 14 | ```xml 15 | 16 | com.nixsolutions 17 | lsd4j 18 | ${lsd4j.version} 19 | 20 | ``` 21 | 22 | Gradle 23 | ```xml 24 | compile group: 'com.nixsolutions', name: 'lsd4j', version: '1.0.SNAPSHOT' 25 | ``` 26 | 27 | ### Quickstart ### 28 | 29 | # Declarative structured logging 30 | 31 | Lsd4j has mechanism to create structured logs with annotation driven approach. This is flexible and easy to use mechanism, that allows you to create structured logs in various ways. 32 | 33 | Main annotation that helps to create logs is **@ContextParam**. With this annotation you can mark method parameter as loggable. This means that parameter which marked with this annotation will be processed with lsd4j mechanism and will be added to context logging map. 34 | 35 | For example you have method doSomething with two parameters and you want to log the second parameter. All that you need is to mark parameter with @ContextParam annotation. 36 | 37 | public void doSomething(String firstParam, @ContextParam User secondParam) { 38 | . . . 39 | } 40 | 41 | ## Structured logs extracting approaches 42 | 43 | Lsd4j has two main concepts of logging logic, which you can use in your application. There are creation of custom extractor and marking class with @LoggableType annotation properties with subtypes of this annotation. Let's go through these methods. 44 | 45 | public class User { 46 | private String name; 47 | private String surname; 48 | private int age; 49 | 50 | . . . 51 | } 52 | 53 | If you want to create structured log for User object, you need to do some preparation. 54 | For creating structured logs for type you need to create extractor for this type or mark your class with **@LoggableType** annotation. 55 | 56 | 57 | 58 | ```mermaid 59 | graph LR 60 | A["@"ContextParam] --> B["@"LoggableType] 61 | A --> C[Extractor<>] 62 | B --> D[interface ContextParamAccessor] 63 | B --> E["@"LoggableType.extractionMethod annotated custom method] 64 | ``` 65 | 66 | ## Extractors 67 | 68 | Let's try to create extractor for class User. LSD4J has base extractor interface - **ContextParamExtractor**. This interface has **extractParams()** and **getExtractableClases()** methods that have to be implemented. extractParams method returns context logging map - Map with fields that need to be logged from your object. 69 | 70 | For example if you want to create log message for name and surname fields from User type, you need to create special extractor for User type, which implements ContextParamExtractor and override extractParams and getExtractableClases methods. 71 | 72 | @Component 73 | public class UserExtractor implements ContextParamExtractor { 74 | @Override 75 | public Map extractParams(String name, User parameter) { 76 | if (name.isEmpty()) 77 | { 78 | return ImmutableMap.of("userName", parameter.name,"userSurname", parameter.surname); 79 | } else { 80 | return ImmutableMap.of(); 81 | } 82 | } 83 | 84 | @Override 85 | public List> getExtractableClasses() { 86 | return Arrays.asList(User.class); 87 | } 88 | } 89 | 90 | With UserExtractor mechanism of lsd4j will understand what fields of User type will be added to context logging map. As a result will be created context logging map with two field: name and surname with corresponding keys. 91 | 92 | ## @LoggableType annotation and methods 93 | 94 | The second way to create structured logs from POJO object is to annotate class with **@LoggableType** annotation. This approach have two ways to be implemented: 95 | 96 | - Implement ContextParamAccessor and override extractParams method. 97 | - Create custom method and annotate it with @LoggableType.extractionMethod annotation 98 | 99 | Firstly we will look at first approach. For example we have User class and we want to log some fields from this class. First of all we need to annotate our class with @LoggableType annotation. This means that the User type will be processed with lsd4j Lookup Strategy (will be reviewed later) and marked as Loggable type. 100 | 101 | The second step is to implement **ContextParamAccessor** on your POJO class, in our case it’s User class and override extractParams method. 102 | 103 | 104 | @LoggableType 105 | public class User implements ContextParamAccessor { 106 | private String name; 107 | private String surname; 108 | private int age; 109 | 110 | @Override 111 | public Map extractParams() { 112 | return ImmutableMap.of("userName", parameter.name,"userSurname", parameter.surname); 113 | } 114 | 115 | In this case implemented from interface method will extract class fields, that mention in this method to context logging map. 116 | 117 | So when some method want to create structured logs from POJO object, with lsd4j mechanism will be called implemented method and will be created context logging map with fields, which was mentioned in extractParams method. 118 | 119 | Lsd4j has similar way to create custom method for extracting class fields to log structured logs. You need to annotate class with @LoggableType annotation and create custom method, which will return Map. But for this case you don’t need to implement ContextParamAccessor interface. All that you need is to annotate your custom method with **@LoggableType.extractionMethod** annotation. 120 | 121 | @LoggableType 122 | public class User { 123 | private String name; 124 | private String surname; 125 | private int age; 126 | 127 | @LoggableType.extractionMethod 128 | public Map extractParams() { 129 | return ImmutableMap.of("userName", parameter.name,"userSurname", parameter.surname); 130 | } 131 | 132 | So when some method want to create structured logs from POJO object, with lsd4j mechanism will be called implemented method and will be created context logging map with fields, which was mentioned in your custom method. 133 | 134 | ## Logging Annotations Overview 135 | 136 | ### @DoLog.entry 137 | 138 | You can annotate public methods with **@Log.entry** annotation to write log message with _DEBUG_ log level when method execution is started. Log message will contain: 139 | * Method name 140 | * Context information for method arguments. Context information for method argument will be included into log message if it is annotated with **[@ContextParam](#extractors)** or/and class of method argument is annotated with **[@LoggableType](#loggabletype-annotation-and-methods)**. 141 | 142 | ### @DoLog.exit 143 | 144 | You can annotate public methods with **@Log.exit** annotation to write log message when method execution is finished. 145 | 146 | Log message will contain: 147 | * Method name 148 | * Context information for method arguments. Context information for method argument will be included into log message if it is annotated with **[@ContextParam](#extractors)** or/and class of method argument is annotated with **[@LoggableType](#loggabletype-annotation-and-methods)**. 149 | * Exception, if method was completed unsuccessfully. 150 | 151 | In exceptional case the log message will have _ERROR_ level, otherwise - _DEBUG_ level. 152 | 153 | ### @DoLog.exectime 154 | 155 | You can annotate public methods with **@Log.exectime** annotation to write log message with _DEBUG_ log level when method was completed. 156 | 157 | This annotation can have next arguments: 158 | * **taskName** - The name of the task which will be displayed in log message (optional argument). If task name was not specified - method name will be used instead of. 159 | * **timeUnit** - time unit for method execution time (optional argument). By default it’s millisecond. 160 | 161 | The log message will contain: 162 | * Task name 163 | * Method duration 164 | * Time unit 165 | 166 | ### @DoLog 167 | 168 | You should annotate method with this annotation if you want enable **[@DoLog.entry](#dologentry)**, **[@DoLog.exit](#dologexit)** and **[@DoLog.exectime](#dologexectime)** annotations. 169 | It means that if you want to log when method started, method finished and execution time you should annotate method in the following way: 170 | ``` 171 | @DoLog 172 | @DoLog.entry 173 | @DoLog.exit 174 | @DoLog.exectime 175 | public void methodA() 176 | { 177 | ... 178 | } 179 | ``` 180 | 181 | TBD -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.nixsolutions' 2 | version '0.1-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | compileJava { 6 | options.compilerArgs << '-parameters' 7 | } 8 | 9 | apply plugin: 'maven' 10 | apply plugin: 'maven-publish' 11 | apply plugin: 'signing' 12 | 13 | sourceCompatibility = 1.8 14 | 15 | repositories { 16 | mavenCentral() 17 | mavenLocal() 18 | maven { 19 | url "https://plugins.gradle.org/m2/" 20 | } 21 | maven { 22 | url 'https://jitpack.io' 23 | } 24 | } 25 | 26 | buildscript { 27 | repositories { 28 | maven { 29 | url "https://plugins.gradle.org/m2/" 30 | } 31 | } 32 | dependencies { 33 | classpath 'net.linguica.gradle:maven-settings-plugin:0.4' 34 | } 35 | } 36 | 37 | apply plugin: 'net.linguica.maven-settings' 38 | 39 | dependencies { 40 | compileOnly group: 'org.springframework', name: 'spring-context', version: '4.3.0.RELEASE' 41 | compileOnly 'org.springframework:spring-aspects:4.3.0.RELEASE' 42 | 43 | compile 'org.slf4j:slf4j-api:1.7.21' 44 | compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4' 45 | compile group: 'com.google.guava', name: 'guava', version: '19.0' 46 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.4.1' 47 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.4.1' 48 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.4.1' 49 | 50 | testCompile group: 'junit', name: 'junit', version: '4.12' 51 | testCompile 'org.springframework:spring-test:4.3.0.RELEASE' 52 | testCompile 'org.springframework:spring-aspects:4.3.0.RELEASE' 53 | testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.0.0' 54 | testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.0.0' 55 | testCompile group: 'org.springframework', name: 'spring-context', version: '4.3.0.RELEASE' 56 | testCompile('com.github.sbrannen:spring-test-junit5:1.2.0') 57 | testCompile group: 'net.logstash.logback', name: 'logstash-logback-encoder', version: '5.2' 58 | testCompile 'ch.qos.logback:logback-core:1.2.3' 59 | testCompile 'ch.qos.logback:logback-classic:1.2.3' 60 | testCompile 'ch.qos.logback:logback-access:1.2.3' 61 | } 62 | 63 | task sourceJar(type: Jar) { 64 | classifier "sources" 65 | from sourceSets.main.allJava 66 | } 67 | 68 | task javadocJar(type: Jar, dependsOn: javadoc) { 69 | classifier "javadoc" 70 | from javadoc.destinationDir 71 | } 72 | 73 | artifacts { 74 | archives jar 75 | archives sourceJar 76 | archives javadocJar 77 | } 78 | 79 | 80 | signing { 81 | sign configurations.archives 82 | } 83 | 84 | publishing { 85 | publications { 86 | mavenJava(MavenPublication) { 87 | customizePom(pom) 88 | groupId 'com.nixsolutions' 89 | artifactId 'lsd4j' 90 | version '0.0.1' 91 | 92 | from components.java 93 | 94 | pom.withXml { 95 | def pomFile = file("${project.buildDir}/generated-pom.xml") 96 | writeTo(pomFile) 97 | def pomAscFile = signing.sign(pomFile).signatureFiles[0] 98 | artifact(pomAscFile) { 99 | classifier = null 100 | extension = 'pom.asc' 101 | } 102 | } 103 | 104 | artifact(sourceJar) { 105 | classifier = 'sources' 106 | } 107 | artifact(javadocJar) { 108 | classifier = 'javadoc' 109 | } 110 | 111 | project.tasks.signArchives.signatureFiles.each { 112 | artifact(it) { 113 | def matcher = it.file =~ /-(sources|javadoc)\.jar\.asc$/ 114 | if (matcher.find()) { 115 | classifier = matcher.group(1) 116 | } else { 117 | classifier = null 118 | } 119 | extension = 'jar.asc' 120 | } 121 | } 122 | } 123 | } 124 | repositories { 125 | maven { 126 | url "https://oss.sonatype.org/service/local/staging/deploy/maven2" 127 | credentials { 128 | username sonatypeUsername 129 | password sonatypePassword 130 | } 131 | } 132 | } 133 | } 134 | 135 | def customizePom(pom) { 136 | pom.withXml { 137 | def root = asNode() 138 | 139 | root.dependencies.removeAll { dep -> 140 | dep.scope == "test" 141 | } 142 | 143 | root.children().last() + { 144 | resolveStrategy = Closure.DELEGATE_FIRST 145 | 146 | description 'Log Structured Data 4 Java' 147 | name 'LSD4J' 148 | url 'https://github.com/nixsolutions/lsd4j' 149 | organization { 150 | name 'NIX Solutions Ltd.' 151 | url 'https://github.com/nixsolutions' 152 | } 153 | issueManagement { 154 | system 'GitHub' 155 | url 'https://github.com/nixsolutions/lsd4j/issues' 156 | } 157 | licenses { 158 | license { 159 | name 'MIT License' 160 | url 'https://github.com/nixsolutions/lsd4j/blob/master/LICENSE' 161 | distribution 'repo' 162 | } 163 | } 164 | scm { 165 | url 'https://github.com/nixsolutions/lsd4j' 166 | connection 'scm:git:git://github.com/nixsolutions/lsd4j.git' 167 | developerConnection 'scm:git:ssh://git@github.com:nixsolutions/lsd4j.git' 168 | } 169 | developers { 170 | developer { 171 | organization 'NIX Solutions Ltd.' 172 | } 173 | } 174 | } 175 | } 176 | } 177 | 178 | model { 179 | tasks.generatePomFileForMavenJavaPublication { 180 | destination = file("$buildDir/generated-pom.xml") 181 | } 182 | tasks.publishMavenJavaPublicationToMavenRepository { 183 | dependsOn project.tasks.signArchives 184 | } 185 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'lsd4j' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/LogContext.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * @param defines a type of a primary context(f.e. for QuoteLogContext it is probably a quoteId: 7 | * {@link java.lang.Long}) 8 | * @param defines a result of a context info processed(filtered,added, etc). Example: {@link java.lang.String} to 9 | * print in log 10 | */ 11 | public interface LogContext 12 | { 13 | E get(T context); 14 | 15 | E get(String message, T context); 16 | 17 | E get(String message, T context, Map params); 18 | 19 | E get(String message, Map params); 20 | 21 | /** 22 | * Compresses params into a single field 23 | * 24 | * @param contextParams - map of params to shrink 25 | * @param fieldName - resulting field name 26 | * @return resulting map 27 | * Example: {quoteId: 123, groupId: 122}, "groupQtContext" - {groupQtContext: [quoteId: 123, groupId: 122]}
28 | */ 29 | Map shrinkParamsAsField(Map contextParams, String fieldName); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/LogContextDefault.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging; 2 | 3 | import java.util.Collections; 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | public class LogContextDefault implements LogContext { 14 | private static final String KEY_VALUE_DELIMITER = "="; 15 | private static final String KEY_VALUE_PAIRS_DELIMITER = ", "; 16 | private static final String MESSAGE_DELIMITER = "ctx:"; 17 | private static final String CONTEXT_KEY = "key"; 18 | private static final String PARAMS_PREFIX = "{"; 19 | private static final String PARAMS_SUFFIX = "}"; 20 | private static final String COMPOSITE_FIELD_PREFIX = "["; 21 | private static final String COMPOSITE_FIELD_SUFFIX = "]"; 22 | 23 | @Override 24 | public String get(Long context) { 25 | return get(StringUtils.EMPTY, context); 26 | } 27 | 28 | @Override 29 | public String get(String message, Long context) { 30 | return get(message, context, Collections.emptyMap()); 31 | } 32 | 33 | @Override 34 | public String get(String message, Long context, Map params) { 35 | String contextPrefix = StringUtils.isNotBlank(message) ? ". " + MESSAGE_DELIMITER : MESSAGE_DELIMITER; 36 | String info = getContextInfoStream(params, context) 37 | .collect(Collectors.joining(KEY_VALUE_PAIRS_DELIMITER, PARAMS_PREFIX, PARAMS_SUFFIX)); 38 | 39 | return String.join(contextPrefix, StringUtils.defaultIfBlank(message, StringUtils.EMPTY), info); 40 | } 41 | 42 | @Override 43 | public String get(String message, Map params) { 44 | return get(message, null, params); 45 | } 46 | 47 | @Override 48 | public Map shrinkParamsAsField(Map contextParams, String fieldName) { 49 | String fieldValue = contextParams.entrySet().stream() 50 | .map(entry -> String.join(KEY_VALUE_DELIMITER, entry.getKey(), String.valueOf(entry.getValue()))) 51 | .collect(Collectors.joining(KEY_VALUE_PAIRS_DELIMITER, COMPOSITE_FIELD_PREFIX, COMPOSITE_FIELD_SUFFIX)); 52 | Map nestedMap = new LinkedHashMap<>(); 53 | nestedMap.put(fieldName, fieldValue); 54 | return Collections.unmodifiableMap(nestedMap); 55 | } 56 | 57 | private Stream getContextInfoStream(Map params, Long context) { 58 | Map newQuoteInfo = addInfo(params, context); 59 | return newQuoteInfo.entrySet() 60 | .stream() 61 | .map(entry -> String.join(KEY_VALUE_DELIMITER, entry.getKey(), String.valueOf(entry.getValue()))); 62 | } 63 | 64 | private Map addInfo(Map quoteInfo, Long context) { 65 | Map infoMap = asMap(context); 66 | Map newInfo = Optional.ofNullable(quoteInfo) 67 | .map(LinkedHashMap::new) 68 | .orElseGet(LinkedHashMap::new); 69 | 70 | infoMap.putAll(newInfo); 71 | return infoMap; 72 | } 73 | 74 | private Map asMap(Long context) { 75 | Map info = new LinkedHashMap<>(); 76 | 77 | if (context == null || context == 0L) { 78 | return info; 79 | } 80 | 81 | info.put(CONTEXT_KEY, String.valueOf(context)); 82 | return info; 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/LogContextJson.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging; 2 | 3 | import java.util.Collections; 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | import org.apache.commons.lang3.StringUtils; 8 | import com.nixsolutions.logging.common.MapUtils; 9 | import com.fasterxml.jackson.core.JsonProcessingException; 10 | 11 | public class LogContextJson implements LogContext 12 | { 13 | private static final String CONTEXT_ID_KEY = "key"; 14 | private static final String CONTEXT_KEY = "ctx"; 15 | private static final String MESSAGE_KEY = "message"; 16 | 17 | @Override 18 | public String get(Long context) 19 | { 20 | return get(StringUtils.EMPTY, context); 21 | } 22 | 23 | @Override 24 | public String get(String message, Long context) 25 | { 26 | return get(message, context, Collections.emptyMap()); 27 | } 28 | 29 | @Override 30 | public String get(String message, Long context, Map params) 31 | { 32 | Map logInfo = asMap(message); 33 | Map contextInfo = getContextInfo(params, context); 34 | logInfo.put(CONTEXT_KEY, contextInfo); 35 | 36 | return createJsonLogMessage(logInfo); 37 | } 38 | 39 | @Override 40 | public String get(String message, Map params) 41 | { 42 | return get(message, null, params); 43 | } 44 | 45 | @Override 46 | public Map shrinkParamsAsField( 47 | Map contextParams, 48 | String fieldName) 49 | { 50 | String fieldValue; 51 | try 52 | { 53 | fieldValue = MapUtils.convertMapToJson(contextParams); 54 | } 55 | catch (JsonProcessingException e) 56 | { 57 | throw new RuntimeException("Can't convert context map to json."); 58 | } 59 | 60 | Map nestedMap = new LinkedHashMap<>(); 61 | nestedMap.put(fieldName, fieldValue); 62 | 63 | return Collections.unmodifiableMap(nestedMap); 64 | } 65 | 66 | private Map getContextInfo(Map params, Long context) 67 | { 68 | return addInfo(params, context); 69 | } 70 | 71 | private Map addInfo(Map quoteInfo, Long context) 72 | { 73 | Map infoMap = asMap(context); 74 | Map newInfo = Optional.ofNullable(quoteInfo) 75 | .map(LinkedHashMap::new) 76 | .orElseGet(LinkedHashMap::new); 77 | 78 | infoMap.putAll(newInfo); 79 | return infoMap; 80 | } 81 | 82 | private String createJsonLogMessage(Map logMap) 83 | { 84 | try 85 | { 86 | return MapUtils.convertMapToJson(logMap); 87 | } 88 | catch (JsonProcessingException e) 89 | { 90 | throw new RuntimeException("Can't convert context map to json."); 91 | } 92 | } 93 | 94 | private Map asMap(Long context) 95 | { 96 | Map info = new LinkedHashMap<>(); 97 | 98 | if (context == null || context == 0L) 99 | { 100 | return info; 101 | } 102 | 103 | info.put(CONTEXT_ID_KEY, String.valueOf(context)); 104 | return info; 105 | } 106 | 107 | private Map asMap(String message) 108 | { 109 | Map messageMap = new LinkedHashMap<>(); 110 | 111 | if (StringUtils.isBlank(message)) 112 | { 113 | return messageMap; 114 | } 115 | 116 | messageMap.put(MESSAGE_KEY, message); 117 | return messageMap; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/LoggingConstants.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging; 2 | 3 | public final class LoggingConstants 4 | { 5 | public static final String TIME_UNIT = "timeUnit"; 6 | public static final String TASK_NAME = "taskName"; 7 | public static final String DURATION = "duration"; 8 | public static final String TIME_LOGGING_CONTEXT = "timeLoggingContext"; 9 | public static final String RETURNED_RESULT = "@return"; 10 | public static final String SINGLE_PROPERTY = "@single"; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/ObjectlessPrettyLoggable.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging; 2 | 3 | import org.slf4j.Logger; 4 | 5 | public class ObjectlessPrettyLoggable implements PrettyLoggable 6 | { 7 | private Logger logger; 8 | private LogContext logContext; 9 | 10 | public ObjectlessPrettyLoggable(Logger logger, LogContext logContext) 11 | { 12 | this.logger = logger; 13 | this.logContext = logContext; 14 | } 15 | 16 | @Override 17 | public Logger getCurrentLogger() 18 | { 19 | return logger; 20 | } 21 | 22 | @Override 23 | public LogContext getLogContext() 24 | { 25 | return logContext; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/PrettyLoggable.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging; 2 | 3 | import java.util.Map; 4 | import org.slf4j.Logger; 5 | 6 | public interface PrettyLoggable 7 | { 8 | Logger getCurrentLogger(); 9 | 10 | LogContext getLogContext(); 11 | 12 | default void logDebug(String message, T context) 13 | { 14 | getCurrentLogger().debug(getLogContext().get(message, context)); 15 | } 16 | 17 | default void logDebug(String message, Map customContext) 18 | { 19 | getCurrentLogger().debug(getLogContext().get(message, customContext)); 20 | } 21 | 22 | default void logDebug(String message, T context, Map customContext) 23 | { 24 | getCurrentLogger().debug(getLogContext().get(message, context, customContext)); 25 | } 26 | 27 | default void logError(String message, T context, Exception e) 28 | { 29 | getCurrentLogger().error(getLogContext().get(message, context), e); 30 | } 31 | 32 | default void logError(String message, Map customContext, Exception e) 33 | { 34 | getCurrentLogger().error(getLogContext().get(message, customContext), e); 35 | } 36 | 37 | default void logError(String message, T context, Map customContext, Exception e) 38 | { 39 | getCurrentLogger().error(getLogContext().get(message, context, customContext), e); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/advice/LoggingAdvice.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import org.aspectj.lang.ProceedingJoinPoint; 7 | import org.aspectj.lang.annotation.Around; 8 | import org.aspectj.lang.annotation.Aspect; 9 | import org.aspectj.lang.reflect.MethodSignature; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Component; 14 | import com.nixsolutions.logging.advice.handler.LogActionHandlerFactory; 15 | import com.nixsolutions.logging.advice.handler.base.AbstractLogActionHandler; 16 | 17 | @Aspect 18 | @Component 19 | public class LoggingAdvice 20 | { 21 | @Autowired 22 | private LogActionHandlerFactory logActionHandlerFactory; 23 | 24 | //TODO check if advised class has it's own implementation of PrettyLoggable 25 | @Around("@annotation(com.nixsolutions.logging.annotation.Log)") 26 | public Object loggingAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable 27 | { 28 | final Object invocationResult; 29 | final Method method = getMethod(proceedingJoinPoint); 30 | final Object originalObject = proceedingJoinPoint.getTarget(); 31 | final Object[] args = proceedingJoinPoint.getArgs(); 32 | final Logger logger = getLoggerForObject(originalObject); 33 | 34 | long beforeCall = System.nanoTime(); 35 | try 36 | { 37 | logActionHandlerFactory.createEntryHandler(logger).perform( 38 | createParamsForEntryLogging(method, args)); 39 | invocationResult = proceedingJoinPoint.proceed(args); 40 | logActionHandlerFactory.createExectimeHandler(logger).perform( 41 | createParamsForExectimeLogging(method, beforeCall, System.nanoTime())); 42 | logActionHandlerFactory.createExitHandler(logger).perform(createParamsForExitLogging(method, null, 43 | invocationResult)); 44 | } 45 | catch (Exception exception) 46 | { 47 | logActionHandlerFactory.createExectimeHandler(logger).perform( 48 | createParamsForExectimeLogging(method, beforeCall, System.nanoTime())); 49 | logActionHandlerFactory.createExitHandler(logger).perform(createParamsForExitLogging(method, exception, null)); 50 | throw exception; 51 | } 52 | 53 | return invocationResult; 54 | } 55 | 56 | private Map createParamsForEntryLogging(Method method, Object[] methodArgs) 57 | { 58 | HashMap parameters = new HashMap<>(); 59 | 60 | parameters.put(AbstractLogActionHandler.METHOD_PARAM, method); 61 | parameters.put(AbstractLogActionHandler.METHOD_ARGS_PARAM, methodArgs); 62 | 63 | return parameters; 64 | } 65 | 66 | private Map createParamsForExectimeLogging(Method method, long beforeCall, long afterCall) 67 | { 68 | HashMap parameters = new HashMap<>(); 69 | 70 | parameters.put(AbstractLogActionHandler.METHOD_PARAM, method); 71 | parameters.put(AbstractLogActionHandler.START_MOMENT_PARAM, beforeCall); 72 | parameters.put(AbstractLogActionHandler.FINISH_MOMENT_PARAM, afterCall); 73 | 74 | return parameters; 75 | } 76 | 77 | private Map createParamsForExitLogging(Method method, Exception exception, Object invocationResult) 78 | { 79 | HashMap parameters = new HashMap<>(); 80 | 81 | parameters.put(AbstractLogActionHandler.METHOD_PARAM, method); 82 | parameters.putIfAbsent(AbstractLogActionHandler.EXCEPTION_PARAM, exception); 83 | parameters.put(AbstractLogActionHandler.INVOCATION_RESULT_PARAM, invocationResult); 84 | 85 | return parameters; 86 | } 87 | 88 | //TODO Logger cache? 89 | private Logger getLoggerForObject(Object originalObject) 90 | { 91 | return LoggerFactory.getLogger(originalObject.getClass()); 92 | } 93 | 94 | private Method getMethod(ProceedingJoinPoint joinPoint) 95 | { 96 | return ((MethodSignature) joinPoint.getSignature()).getMethod(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/advice/handler/LogActionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.handler; 2 | 3 | import java.util.Map; 4 | 5 | public interface LogActionHandler 6 | { 7 | void perform(Map params); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/advice/handler/LogActionHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.handler; 2 | 3 | import org.slf4j.Logger; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Component; 6 | import com.nixsolutions.logging.LogContext; 7 | import com.nixsolutions.logging.ObjectlessPrettyLoggable; 8 | import com.nixsolutions.logging.PrettyLoggable; 9 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractorFactory; 10 | import com.nixsolutions.logging.parameters.loggabletype.util.AnnotationReflectionLookupUtils; 11 | 12 | @Component 13 | public class LogActionHandlerFactory 14 | { 15 | private AnnotationReflectionLookupUtils reflectionLookupUtils; 16 | private ContextParamExtractorFactory contextParamExtractorFactory; 17 | private LogContext logContext; 18 | 19 | @Autowired 20 | public LogActionHandlerFactory(AnnotationReflectionLookupUtils reflectionLookupUtils, 21 | ContextParamExtractorFactory contextParamExtractorFactory, 22 | LogContext logContext) 23 | { 24 | this.reflectionLookupUtils = reflectionLookupUtils; 25 | this.contextParamExtractorFactory = contextParamExtractorFactory; 26 | this.logContext = logContext; 27 | } 28 | 29 | public LogActionHandler createExectimeHandler(Logger logger) 30 | { 31 | PrettyLoggable prettyLoggable = new ObjectlessPrettyLoggable(logger, logContext); 32 | return new LogExectimeActionHandler(prettyLoggable); 33 | } 34 | 35 | public LogActionHandler createEntryHandler(Logger logger) 36 | { 37 | PrettyLoggable prettyLoggable = new ObjectlessPrettyLoggable(logger, logContext); 38 | return new LogEntryActionHandler(prettyLoggable, reflectionLookupUtils); 39 | } 40 | 41 | public LogActionHandler createExitHandler(Logger logger) 42 | { 43 | PrettyLoggable prettyLoggable = new ObjectlessPrettyLoggable(logger, logContext); 44 | return new LogExitActionHandler(prettyLoggable, reflectionLookupUtils); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/advice/handler/LogEntryActionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.handler; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Map; 5 | import com.nixsolutions.logging.PrettyLoggable; 6 | import com.nixsolutions.logging.advice.handler.base.LogFlowActionHandler; 7 | import com.nixsolutions.logging.annotation.Log; 8 | import com.nixsolutions.logging.parameters.loggabletype.util.AnnotationReflectionLookupUtils; 9 | 10 | public class LogEntryActionHandler extends LogFlowActionHandler 11 | { 12 | public LogEntryActionHandler(PrettyLoggable prettyLoggable, 13 | AnnotationReflectionLookupUtils reflectionLookupUtils) 14 | { 15 | super(prettyLoggable, reflectionLookupUtils); 16 | } 17 | 18 | @Override 19 | public void perform(Map params) 20 | { 21 | Method method = (Method) params.get(METHOD_PARAM); 22 | if (!isApplicable(method, Log.entry.class)) 23 | { 24 | return; 25 | } 26 | 27 | Object[] args = (Object[]) params.get(METHOD_ARGS_PARAM); 28 | 29 | prettyLoggable.logDebug(method.getName() + "() -- >", getAdditionalContextInfo(method.getParameters(), args)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/advice/handler/LogExectimeActionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.handler; 2 | 3 | import static com.nixsolutions.logging.LoggingConstants.DURATION; 4 | import static com.nixsolutions.logging.LoggingConstants.TASK_NAME; 5 | import static com.nixsolutions.logging.LoggingConstants.TIME_LOGGING_CONTEXT; 6 | import static com.nixsolutions.logging.LoggingConstants.TIME_UNIT; 7 | import static java.util.Collections.singletonMap; 8 | import static java.util.concurrent.TimeUnit.NANOSECONDS; 9 | import static org.apache.commons.lang3.StringUtils.SPACE; 10 | import static org.apache.commons.lang3.StringUtils.isNotEmpty; 11 | import java.lang.reflect.Method; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.concurrent.TimeUnit; 15 | import com.nixsolutions.logging.PrettyLoggable; 16 | import com.nixsolutions.logging.advice.handler.base.AbstractLogActionHandler; 17 | import com.nixsolutions.logging.annotation.Log; 18 | import com.nixsolutions.logging.common.WordUtils; 19 | 20 | public class LogExectimeActionHandler extends AbstractLogActionHandler 21 | { 22 | public LogExectimeActionHandler(PrettyLoggable prettyLoggable) 23 | { 24 | super(prettyLoggable); 25 | } 26 | 27 | @Override 28 | public void perform(Map params) 29 | { 30 | Method method = (Method) params.get(METHOD_PARAM); 31 | if (!isApplicable(method, Log.exectime.class)) 32 | { 33 | return; 34 | } 35 | 36 | Long startTime = (Long) params.get(START_MOMENT_PARAM); 37 | Long endTime = (Long) params.get(FINISH_MOMENT_PARAM); 38 | TimeUnit timeUnit = method.getAnnotation(Log.exectime.class).timeUnit(); 39 | String taskName = getTaskNameIfPresentOrMethodName(method); 40 | 41 | Map durationContextInfo = getDurationAsContextInfo(taskName, timeUnit, startTime, endTime); 42 | 43 | prettyLoggable.logDebug("execution finished", durationContextInfo); 44 | } 45 | 46 | private Map getDurationAsContextInfo(String taskName, TimeUnit timeUnit, long beforeCall, long 47 | afterCall) 48 | { 49 | Map timeLoggingContext = new HashMap<>(); 50 | 51 | timeLoggingContext.put(TIME_UNIT, timeUnit.name()); 52 | timeLoggingContext.put(TASK_NAME, taskName); 53 | timeLoggingContext.put(DURATION, getDesiredDurationFromNanoseconds(beforeCall, afterCall, timeUnit)); 54 | 55 | return singletonMap(TIME_LOGGING_CONTEXT, timeLoggingContext); 56 | } 57 | 58 | private String getTaskNameIfPresentOrMethodName(Method method) 59 | { 60 | Log.exectime annotation = method.getAnnotation(Log.exectime.class); 61 | 62 | if (isNotEmpty(annotation.taskName())) 63 | { 64 | return WordUtils.toCamelCase(annotation.taskName(), SPACE); 65 | } 66 | return method.getName(); 67 | } 68 | 69 | private long getDesiredDurationFromNanoseconds(long start, long end, TimeUnit timeUnit) 70 | { 71 | return timeUnit.convert(end - start, NANOSECONDS); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/advice/handler/LogExitActionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.handler; 2 | 3 | import static com.nixsolutions.logging.LoggingConstants.RETURNED_RESULT; 4 | import static java.util.Collections.EMPTY_MAP; 5 | import static java.util.Collections.singletonMap; 6 | import static java.util.Objects.nonNull; 7 | import java.lang.reflect.Method; 8 | import java.util.Collections; 9 | import java.util.Map; 10 | import com.nixsolutions.logging.PrettyLoggable; 11 | import com.nixsolutions.logging.advice.handler.base.LogFlowActionHandler; 12 | import com.nixsolutions.logging.annotation.Log; 13 | import com.nixsolutions.logging.annotation.LoggableType; 14 | import com.nixsolutions.logging.parameters.loggabletype.AnnotatedObject; 15 | import com.nixsolutions.logging.parameters.loggabletype.util.AnnotationReflectionLookupUtils; 16 | 17 | public class LogExitActionHandler extends LogFlowActionHandler 18 | { 19 | 20 | private AnnotationReflectionLookupUtils reflectionLookupUtils; 21 | 22 | public LogExitActionHandler(PrettyLoggable prettyLoggable, AnnotationReflectionLookupUtils reflectionLookupUtils) 23 | { 24 | super(prettyLoggable, reflectionLookupUtils); 25 | } 26 | 27 | @Override 28 | public void perform(Map params) 29 | { 30 | Method method = (Method) params.get(METHOD_PARAM); 31 | if (!isApplicable(method, Log.exit.class)) 32 | { 33 | return; 34 | } 35 | 36 | Exception exception = (Exception) params.get(EXCEPTION_PARAM); 37 | if (nonNull(exception)) 38 | { 39 | prettyLoggable.logError(exception.getMessage(), EMPTY_MAP, exception); 40 | return; 41 | } 42 | 43 | Map contextParams = getContextParams(method, params.get(INVOCATION_RESULT_PARAM)); 44 | prettyLoggable.logDebug("< -- " + method.getName() + "()", contextParams); 45 | } 46 | 47 | private Map getContextParams(Method method, Object invocationResult) 48 | { 49 | if (isVoidReturn(method.getReturnType())) 50 | { 51 | return Collections.singletonMap(RETURNED_RESULT, "void"); 52 | } 53 | 54 | return getLoggableTypesContextInfo(singletonMap(RETURNED_RESULT, 55 | AnnotatedObject.createWithAnnotation(invocationResult, LoggableType.class))); 56 | } 57 | 58 | private boolean isVoidReturn(Class returnType) 59 | { 60 | return returnType.equals(void.class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/advice/handler/base/AbstractLogActionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.handler.base; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | import com.nixsolutions.logging.PrettyLoggable; 6 | import com.nixsolutions.logging.advice.handler.LogActionHandler; 7 | import com.nixsolutions.logging.annotation.Log; 8 | 9 | public abstract class AbstractLogActionHandler implements LogActionHandler 10 | { 11 | public static final String EXCEPTION_PARAM = "exceptionThrown"; 12 | public static final String START_MOMENT_PARAM = "startMoment"; 13 | public static final String FINISH_MOMENT_PARAM = "finishMoment"; 14 | public static final String INVOCATION_RESULT_PARAM = "invocationResult"; 15 | public static final String METHOD_ARGS_PARAM = "methodArgs"; 16 | public static final String METHOD_PARAM = "method"; 17 | 18 | protected PrettyLoggable prettyLoggable; 19 | 20 | protected AbstractLogActionHandler(PrettyLoggable prettyLoggable) 21 | { 22 | this.prettyLoggable = prettyLoggable; 23 | } 24 | 25 | protected boolean isApplicable(Method method, Class desiredAnnotation) 26 | { 27 | return method.isAnnotationPresent(Log.class) 28 | && method.isAnnotationPresent(desiredAnnotation); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/advice/handler/base/LogFlowActionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.handler.base; 2 | 3 | import static com.nixsolutions.logging.LoggingConstants.SINGLE_PROPERTY; 4 | import static java.util.Objects.nonNull; 5 | import static java.util.stream.Collectors.toMap; 6 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 7 | import java.lang.reflect.Parameter; 8 | import java.util.Map; 9 | import java.util.Map.Entry; 10 | import org.apache.commons.lang3.tuple.Pair; 11 | import com.nixsolutions.logging.PrettyLoggable; 12 | import com.nixsolutions.logging.annotation.ContextParam; 13 | import com.nixsolutions.logging.annotation.LoggableType; 14 | import com.nixsolutions.logging.common.MapUtils; 15 | import com.nixsolutions.logging.parameters.loggabletype.AnnotatedObject; 16 | import com.nixsolutions.logging.parameters.loggabletype.LookupResult; 17 | import com.nixsolutions.logging.parameters.loggabletype.util.AnnotationReflectionLookupUtils; 18 | 19 | public abstract class LogFlowActionHandler extends AbstractLogActionHandler 20 | { 21 | protected AnnotationReflectionLookupUtils reflectionLookupUtils; 22 | 23 | public LogFlowActionHandler(PrettyLoggable prettyLoggable, AnnotationReflectionLookupUtils reflectionLookupUtils) 24 | { 25 | super(prettyLoggable); 26 | this.reflectionLookupUtils = reflectionLookupUtils; 27 | } 28 | 29 | protected Map getAdditionalContextInfo(Parameter[] methodParameters, Object[] methodArguments) 30 | { 31 | Map> nameAnnotatedObjectMap = MapUtils 32 | .toImmutableMap(methodParameters, methodArguments).entrySet().stream() 33 | .filter(entry -> entry.getKey().isAnnotationPresent(ContextParam.class)) 34 | .map(this::toNameAnnotatedObject) 35 | .collect(toMap(Pair::getKey, Pair::getValue)); 36 | 37 | return getLoggableTypesContextInfo(nameAnnotatedObjectMap); 38 | } 39 | 40 | protected Map getLoggableTypesContextInfo( 41 | Map> contextParamAnnotatedObjectMap) 42 | { 43 | return contextParamAnnotatedObjectMap.entrySet().stream() 44 | .map(this::toNamedLookup) 45 | .filter(this::isResolved) 46 | .map(this::toNameResult) 47 | .collect(toMap(Pair::getKey, Pair::getValue)); 48 | } 49 | 50 | private Pair toNamedLookup(Entry> entry) 51 | { 52 | return Pair.of(entry.getKey(), reflectionLookupUtils.strategyLookupForRootObj(entry.getValue())); 53 | } 54 | 55 | private boolean isResolved(Pair entry) 56 | { 57 | return entry.getValue().isResolved(); 58 | } 59 | 60 | private Pair toNameResult(Pair entry) 61 | { 62 | Map contextParams = entry.getRight().executeForResult(); 63 | if (isSinglePropertyContextParams(contextParams)) 64 | { 65 | return Pair.of(entry.getLeft(), contextParams.get(SINGLE_PROPERTY)); 66 | } 67 | 68 | return Pair.of(entry.getLeft(), contextParams); 69 | } 70 | 71 | private boolean isSinglePropertyContextParams(Map contextParams) 72 | { 73 | return contextParams.size() == 1 && nonNull(contextParams.get(SINGLE_PROPERTY)); 74 | } 75 | 76 | private Pair> toNameAnnotatedObject(Entry 77 | parameterObjectEntry) 78 | { 79 | ContextParam annotation = parameterObjectEntry.getKey().getAnnotation(ContextParam.class); 80 | String paramName = parameterObjectEntry.getKey().getName(); 81 | if (isNotBlank(annotation.value())) 82 | { 83 | paramName = annotation.value(); 84 | } 85 | 86 | return Pair.of(paramName, 87 | AnnotatedObject.createWithAnnotation(parameterObjectEntry.getValue(), LoggableType.class)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/annotation/ContextParam.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 8 | 9 | /** 10 | * Use this annotation to mark the parameter you want to add into log message as context parameter. 11 | * For this an appropriate {@link ContextParamExtractor} will be used. 12 | *
13 | * {@code value} is used as name for name of the field primarily. Otherwise, method 14 | * parameter name is used. 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.PARAMETER, ElementType.TYPE_USE}) 18 | public @interface ContextParam 19 | { 20 | String value() default ""; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/annotation/Log.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * Annotation intended to be a generic one for logging advice: 11 | * 1) Log entry into the method 12 | * 2) Log exit from the method(with exec time logging or not) 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(ElementType.METHOD) 16 | public @interface Log 17 | { 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @interface entry 20 | { 21 | } 22 | 23 | @Retention(RetentionPolicy.RUNTIME) 24 | @interface exit 25 | { 26 | } 27 | 28 | @Retention(RetentionPolicy.RUNTIME) 29 | @interface exectime 30 | { 31 | String taskName() default ""; 32 | 33 | TimeUnit timeUnit() default TimeUnit.MILLISECONDS; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/annotation/LoggableType.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import com.nixsolutions.logging.parameters.loggabletype.ExtractionResolutionStrategy; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target(ElementType.TYPE) 11 | public @interface LoggableType { 12 | boolean ignoreParents() default true; 13 | 14 | ExtractionResolutionStrategy resolutionStrategy() default ExtractionResolutionStrategy.COLLECTOR_FIRST; 15 | 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.FIELD, ElementType.METHOD}) 18 | @interface property { 19 | String name() default ""; 20 | } 21 | 22 | @Retention(RetentionPolicy.RUNTIME) 23 | @Target({ElementType.METHOD}) 24 | @interface extractionMethod { 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/common/MapUtils.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.common; 2 | 3 | import static org.apache.commons.lang3.StringUtils.defaultString; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | import java.util.function.BinaryOperator; 10 | import java.util.stream.Collectors; 11 | import org.apache.commons.lang3.StringUtils; 12 | import com.fasterxml.jackson.core.JsonProcessingException; 13 | import com.fasterxml.jackson.databind.ObjectMapper; 14 | 15 | /** 16 | * Provides utility methods for Map instances. 17 | */ 18 | public class MapUtils 19 | { 20 | private static final BinaryOperator DEFAULT_MERGE_FUNCTION = (value1, value2) -> value2; 21 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 22 | 23 | /** 24 | * Returns an immutable empty map if the argument is null, 25 | * or the argument itself otherwise. 26 | * 27 | * @param the key type 28 | * @param the value type 29 | * @param map the map, possibly null 30 | * @return an empty map if the argument is null 31 | */ 32 | public static Map emptyIfNull(final Map map) 33 | { 34 | return map == null ? new HashMap() : map; 35 | } 36 | 37 | /** 38 | * Returns either the passed in map, or if the map is {@code null}, 39 | * the value of {@code defaultMap}. 40 | * 41 | * @param the key type 42 | * @param the value type 43 | * @param map the map, possibly null 44 | * @param defaultMap the returned values if map is {@code null} 45 | * @return an empty list if the argument is null 46 | * @since 4.0 47 | */ 48 | public static Map defaultIfNull(final Map map, final Map defaultMap) 49 | { 50 | return map == null ? defaultMap : map; 51 | } 52 | 53 | /** 54 | * Map values to lower case. 55 | * Null values will be updated to empty strings 56 | */ 57 | public static Map lowCaseMapValues(Map map) 58 | { 59 | return map.entrySet().stream() 60 | .collect(Collectors.toMap(Map.Entry::getKey, 61 | entry -> StringUtils.lowerCase(defaultString(entry.getValue())))); 62 | } 63 | 64 | /** 65 | * Method converts array of {@code keys} and {@code values} into an immutable map 66 | * 67 | * @param keys 68 | * @param values 69 | * @return resulting map 70 | */ 71 | public static Map toImmutableMap(K[] keys, V[] values) 72 | { 73 | return makeMapImmutable(toMap(keys, values)); 74 | } 75 | 76 | /** 77 | * Method converts array of {@code keys} and {@code values} into a mutable map 78 | * 79 | * @param keys keys 80 | * @param values values 81 | * @return resulting map 82 | */ 83 | public static Map toMutableMap(K[] keys, V[] values) 84 | { 85 | return toMap(keys, values); 86 | } 87 | 88 | private static Map toMap(K[] keys, V[] values) 89 | { 90 | Map map = new HashMap<>(); 91 | int keysLength = (keys != null) ? keys.length : 0; 92 | int valuesLength = (values != null) ? values.length : 0; 93 | 94 | if (keysLength != valuesLength) 95 | { 96 | throw new IllegalArgumentException("The number of keys doesn't match the number of values."); 97 | } 98 | 99 | for (int i = 0; i < keysLength; i++) 100 | { 101 | map.put(keys[i], values[i]); 102 | } 103 | 104 | return map; 105 | } 106 | 107 | private static Map makeMapImmutable(Map map) 108 | { 109 | return Collections.unmodifiableMap(new LinkedHashMap<>(map)); 110 | } 111 | 112 | /** 113 | * Collects maps' keys and values into new map using {@code mergeFunction} to resolve conflicts 114 | * 115 | * @param maps 116 | * @param mergeFunction 117 | * @return resulting map 118 | */ 119 | @SafeVarargs 120 | public static Map mergeMaps(BinaryOperator mergeFunction, Map... maps) 121 | { 122 | return Arrays.stream(maps) 123 | .flatMap(map -> map.entrySet().stream()) 124 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, mergeFunction)); 125 | } 126 | 127 | /** 128 | * Collects maps' keys and values into new map using default merge function: {@code (v1, v2) -> v2} 129 | * which works as default i.e. rewriting values under non-unique keys. 130 | * 131 | * @param maps 132 | * @return resulting map 133 | */ 134 | @SafeVarargs 135 | public static Map mergeMaps(Map... maps) 136 | { 137 | return mergeMaps(DEFAULT_MERGE_FUNCTION, maps); 138 | } 139 | 140 | /** 141 | * Method converts {@code params} context map 'String, Object' into JSON String 142 | * 143 | * @param params context map 144 | * @return JSON String 145 | * */ 146 | public static String convertMapToJson(Map params) throws JsonProcessingException 147 | { 148 | return OBJECT_MAPPER.writer().writeValueAsString(params); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/common/WordUtils.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.common; 2 | 3 | import static org.apache.commons.lang3.StringUtils.EMPTY; 4 | import static org.apache.commons.lang3.text.WordUtils.capitalizeFully; 5 | import static org.apache.commons.lang3.text.WordUtils.uncapitalize; 6 | import java.text.BreakIterator; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | public class WordUtils 10 | { 11 | /** 12 | * Truncate string on closest word boundary. 13 | *

14 | *

15 |    *   WordUtils.truncateWithWordBoundary(null, *) = ""
16 |    *   WordUtils.truncateWithWordBoundary(*, 0) = ""
17 |    *   WordUtils.truncateWithWordBoundary(*, -1) = ""
18 |    *   WordUtils.truncateWithWordBoundary("abc", 5) = "abc"
19 |    *   WordUtils.truncateWithWordBoundary("abc dfe", 5) = "abc"
20 |    *   WordUtils.truncateWithWordBoundary("abc,:;dfc", 5) = "abc
21 |    * 
22 | * 23 | * @param string - the String to be truncated, may be null 24 | * @param maxLength - max length of truncated string 25 | * @return same string if string length less then max length or truncated string 26 | */ 27 | public static String truncateWithWordBoundary(String string, int maxLength) 28 | { 29 | if (StringUtils.isBlank(string) || maxLength <= 0) 30 | { 31 | return EMPTY; 32 | } 33 | 34 | if (string.length() < maxLength) 35 | { 36 | return string; 37 | } 38 | 39 | BreakIterator breakIterator = BreakIterator.getWordInstance(); 40 | breakIterator.setText(string); 41 | 42 | int currentWordStart = breakIterator.preceding(maxLength); 43 | 44 | return string.substring(0, breakIterator.following(currentWordStart - 2)); 45 | } 46 | 47 | /** 48 | * Converts {@code sourceString} to a camelCase string using {@code delimiters} 49 | * Examples: 50 | *
    51 | *
  • toCamelCase("Load renewal"," ") - loadRenewal
  • 52 | *
  • toCamelCase("Load renewal! Now"," !") - loadRenewalNow
  • 53 | *
  • toCamelCase("Load ReNEwaL"," ") - loadRenewal
  • 54 | *
  • toCamelCase("Loadrenewal","") - loadrenewal
  • 55 | *
56 | * 57 | * @param sourceString 58 | * @param delimiters - delimiters in a single string 59 | */ 60 | public static String toCamelCase(String sourceString, String delimiters) 61 | { 62 | String result = capitalizeFully(sourceString, delimiters.toCharArray()); 63 | for (String delimiter : delimiters.split(EMPTY)) 64 | { 65 | result = result.replaceAll(delimiter, EMPTY); 66 | } 67 | return uncapitalize(result); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/configuration/ContextExtractorFactoryConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.configuration; 2 | 3 | import static java.util.Collections.EMPTY_MAP; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Objects; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 12 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractorFactory; 13 | 14 | @Configuration 15 | public class ContextExtractorFactoryConfiguration 16 | { 17 | private static final ContextParamExtractor DEFAULT_CONTEXT_PARAM_EXTRACTOR = 18 | new ContextParamExtractor() 19 | { 20 | @Override 21 | public Map extractParams(String name, Object parameter) 22 | { 23 | return EMPTY_MAP; 24 | } 25 | 26 | @Override 27 | public List> getExtractableClasses() 28 | { 29 | throw new UnsupportedOperationException(); 30 | } 31 | }; 32 | 33 | @Bean 34 | public ContextParamExtractorFactory contextParamExtractorFactory( 35 | @Autowired List contextParamExtractorsList, 36 | @Autowired(required = false) @Qualifier("defaultExtractor") ContextParamExtractor defaultExtractor) 37 | { 38 | ContextParamExtractorFactory contextParamExtractorFactory = 39 | new ContextParamExtractorFactory(contextParamExtractorsList); 40 | 41 | if (Objects.isNull(defaultExtractor)) 42 | { 43 | contextParamExtractorFactory.setDefaultContextParamExtractor(DEFAULT_CONTEXT_PARAM_EXTRACTOR); 44 | } 45 | else 46 | { 47 | contextParamExtractorFactory.setDefaultContextParamExtractor(defaultExtractor); 48 | } 49 | 50 | return contextParamExtractorFactory; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/configuration/LoggingConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.configuration; 2 | 3 | 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | @Configuration 9 | @Import(ContextExtractorFactoryConfiguration.class) 10 | @ComponentScan("com.nixsolutions.logging") 11 | public class LoggingConfiguration 12 | { 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/extractor/ContextParamExtractor.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.extractor; 2 | 3 | import static org.apache.commons.lang3.StringUtils.EMPTY; 4 | import static org.apache.commons.lang3.StringUtils.defaultIfBlank; 5 | import java.lang.reflect.Parameter; 6 | import java.util.List; 7 | import java.util.Map; 8 | import com.nixsolutions.logging.annotation.ContextParam; 9 | 10 | /** 11 | * Basic interface for specific class parameter extractor. 12 | *
13 | * ctx stands for context 14 | *
15 | * Can extract parameters as Map in following ways: 16 | *
    17 | *
  • As method parameter({@link Parameter}) and value: 18 | * {@link #extractParams(Parameter, Object)}
    19 | * If name is provided in an appropriate {@link ContextParam} 20 | * annotation it is used as name for a ctx param, otherwise - method param's name. 21 | *
  • 22 | *
  • As value itself: {@link #extractParams(Object)} - suitable for extracting multiple ctx parameters in a map 23 | *
  • 24 | *
  • As name and value: {@link #extractParams(String, Object)} - consider the previous option - here you can nest 25 | * your ctx parameters and give them a name
  • 26 | *
27 | *
28 | * To include your custom extractor make it manageable by Spring. 29 | * 30 | * @param class which will be extracted for context params 31 | */ 32 | public interface ContextParamExtractor 33 | { 34 | default Map extractParams(final E parameter) 35 | { 36 | return extractParams(EMPTY, parameter); 37 | } 38 | 39 | Map extractParams(String name, final E parameter); 40 | 41 | default Map extractParams(Parameter parameter, final E parameterValue) 42 | { 43 | return extractParams(defaultIfBlank( 44 | parameter.getAnnotation(ContextParam.class).value(), 45 | parameter.getName()), 46 | parameterValue); 47 | } 48 | 49 | /** 50 | * @return list of classes that can be maintained by this extractor 51 | */ 52 | List> getExtractableClasses(); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/extractor/ContextParamExtractorFactory.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.extractor; 2 | 3 | import java.util.AbstractMap.SimpleEntry; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Map.Entry; 7 | import java.util.Optional; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | @SuppressWarnings("unchecked") 12 | public class ContextParamExtractorFactory 13 | { 14 | private ContextParamExtractor defaultContextParamExtractor; 15 | 16 | private Map, ContextParamExtractor> contextParamExtractors; 17 | 18 | public ContextParamExtractorFactory(List contextParamExtractorsList) 19 | { 20 | contextParamExtractors = createParameterExtractorMap(contextParamExtractorsList); 21 | } 22 | 23 | private Map, ContextParamExtractor> createParameterExtractorMap( 24 | List contextParamExtractors) 25 | { 26 | return contextParamExtractors.stream() 27 | .flatMap(this::getParamExtractorsEntries) 28 | .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); 29 | } 30 | 31 | private Stream, ? extends ContextParamExtractor>> getParamExtractorsEntries( 32 | ContextParamExtractor contextParamExtractor) 33 | { 34 | return contextParamExtractor.getExtractableClasses().stream() 35 | .map(clazz -> 36 | new SimpleEntry, ContextParamExtractor>(clazz, contextParamExtractor)); 37 | } 38 | 39 | public ContextParamExtractor getExtractorByClassSafe(Class clazz) 40 | { 41 | return Optional.ofNullable(contextParamExtractors.get(clazz)) 42 | .orElse((ContextParamExtractor) defaultContextParamExtractor); 43 | } 44 | 45 | public ContextParamExtractor getExtractorByClass(Class clazz) 46 | { 47 | return (ContextParamExtractor) contextParamExtractors.get(clazz); 48 | } 49 | 50 | public void setDefaultContextParamExtractor(ContextParamExtractor defaultContextParamExtractor) 51 | { 52 | this.defaultContextParamExtractor = defaultContextParamExtractor; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/extractor/impl/LongContextParamExtractor.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.extractor.impl; 2 | 3 | import static java.util.Collections.singletonMap; 4 | import static java.util.Collections.unmodifiableList; 5 | import static java.util.Collections.unmodifiableMap; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Map; 9 | import org.springframework.stereotype.Component; 10 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 11 | 12 | @Component 13 | public class LongContextParamExtractor implements ContextParamExtractor 14 | { 15 | @Override 16 | public Map extractParams(String name, Long parameterValue) 17 | { 18 | return unmodifiableMap(singletonMap(name, String.valueOf(parameterValue))); 19 | } 20 | 21 | @Override 22 | public List> getExtractableClasses() 23 | { 24 | return unmodifiableList(Arrays.asList(Long.class, long.class)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/extractor/impl/StringContextParamExtractor.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.extractor.impl; 2 | 3 | import static java.util.Collections.singletonList; 4 | import static java.util.Collections.singletonMap; 5 | import static java.util.Collections.unmodifiableList; 6 | import static java.util.Collections.unmodifiableMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import org.springframework.stereotype.Component; 10 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 11 | 12 | @Component 13 | public class StringContextParamExtractor implements ContextParamExtractor 14 | { 15 | @Override 16 | public Map extractParams(String name, String parameter) 17 | { 18 | return unmodifiableMap(singletonMap(name, parameter)); 19 | } 20 | 21 | @Override 22 | public List> getExtractableClasses() 23 | { 24 | return unmodifiableList(singletonList(String.class)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/AnnotatedObject.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.util.function.Supplier; 5 | 6 | public class AnnotatedObject 7 | { 8 | 9 | private Object object; 10 | private A annotation; 11 | 12 | private AnnotatedObject(Object object, A annotation) 13 | { 14 | this.object = object; 15 | this.annotation = annotation; 16 | } 17 | 18 | public static AnnotatedObject createWithAnnotation(Object object, Class annotationClass) 19 | { 20 | return new AnnotatedObject<>(object, object.getClass().getAnnotation(annotationClass)); 21 | } 22 | 23 | public static AnnotatedObject createWithAnnotationMethod(Object object, Supplier 24 | getAnnotationMethod) 25 | { 26 | return new AnnotatedObject<>(object, getAnnotationMethod.get()); 27 | } 28 | 29 | public boolean isAnnotated() 30 | { 31 | return annotation != null; 32 | } 33 | 34 | public Object getObject() 35 | { 36 | return object; 37 | } 38 | 39 | public A getAnnotation() 40 | { 41 | return annotation; 42 | } 43 | 44 | public Class getObjectClass() 45 | { 46 | return object.getClass(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/ContextParamsAccessor.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype; 2 | 3 | import java.util.Map; 4 | 5 | public interface ContextParamsAccessor 6 | { 7 | Map extractParams(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/ExtractionResolutionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype; 2 | 3 | public enum ExtractionResolutionStrategy { 4 | EXTRACTOR_FIRST, 5 | COLLECTOR_FIRST, 6 | RAISE_EX_ON_CONFLICT, 7 | DO_NOTHING 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/LookupResult.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype; 2 | 3 | import static com.nixsolutions.logging.parameters.loggabletype.LookupResult.LookupType.EXCEPTIONAL; 4 | import static com.nixsolutions.logging.parameters.loggabletype.LookupResult.LookupType.LAZY; 5 | import static com.nixsolutions.logging.parameters.loggabletype.LookupResult.LookupType.RESOLVED; 6 | import static com.nixsolutions.logging.parameters.loggabletype.LookupResult.LookupType.UNRESOLVED; 7 | import static java.util.Objects.isNull; 8 | import java.util.Map; 9 | import java.util.function.Function; 10 | import java.util.function.Supplier; 11 | import com.nixsolutions.logging.parameters.loggabletype.exception.UnresolvedLookupException; 12 | 13 | public class LookupResult 14 | { 15 | 16 | private static class ResultAccessor 17 | { 18 | private Function> extractionFunction; 19 | private Object object; 20 | 21 | private Supplier> extractionSupplier; 22 | 23 | private ResultAccessor(Function> extractionFunction, Object object) 24 | { 25 | this.extractionFunction = extractionFunction; 26 | this.object = object; 27 | } 28 | 29 | private ResultAccessor(Supplier> extractionSupplier) 30 | { 31 | this.extractionSupplier = extractionSupplier; 32 | } 33 | 34 | public static ResultAccessor from(Function> extractionFunction, Object object) 35 | { 36 | return new ResultAccessor(extractionFunction, object); 37 | } 38 | 39 | public static ResultAccessor from(Supplier> extractionSupplier) 40 | { 41 | return new ResultAccessor(extractionSupplier); 42 | } 43 | 44 | public Map accessResult() 45 | { 46 | return isNull(extractionSupplier) ? 47 | extractionFunction.apply(object) : 48 | extractionSupplier.get(); 49 | } 50 | } 51 | 52 | public enum LookupType 53 | { 54 | EXCEPTIONAL, RESOLVED, UNRESOLVED, LAZY 55 | } 56 | 57 | private ResultAccessor resultAccessor; 58 | private LookupType lookupType; 59 | private Supplier wrappedLookupResultSupplier; 60 | 61 | private LookupResult(Supplier wrappedLookupResultSupplier) 62 | { 63 | this.wrappedLookupResultSupplier = wrappedLookupResultSupplier; 64 | this.lookupType = LAZY; 65 | } 66 | 67 | private LookupResult(ResultAccessor resultAccessor) 68 | { 69 | this(resultAccessor, LAZY); 70 | } 71 | 72 | private LookupResult(ResultAccessor resultAccessor, LookupType lookupType) 73 | { 74 | this.resultAccessor = resultAccessor; 75 | this.lookupType = lookupType; 76 | } 77 | 78 | public boolean isCertainLookupType(LookupType lookupType) 79 | { 80 | unWrapLazyLookup(); 81 | return this.lookupType.equals(lookupType); 82 | } 83 | 84 | /** 85 | * @return 86 | * @throws UnresolvedLookupException if lookup Is unresolved 87 | * @throws RuntimeException for all user defined exceptions 88 | */ 89 | public Map executeForResult() 90 | { 91 | LookupResult finalLookup = unWrapLazyLookup(); 92 | return finalLookup.resultAccessor.accessResult(); 93 | } 94 | 95 | private LookupResult unWrapLazyLookup() 96 | { 97 | LookupResult finalLookup = this; 98 | while (finalLookup.lookupType.equals(LAZY)) 99 | { 100 | finalLookup = finalLookup.wrappedLookupResultSupplier.get(); 101 | } 102 | 103 | this.lookupType = finalLookup.getLookupType(); 104 | this.resultAccessor = finalLookup.resultAccessor; 105 | 106 | return finalLookup; 107 | } 108 | 109 | public static LookupResult createResolved(Supplier> extractionSupplier) 110 | { 111 | return new LookupResult(ResultAccessor.from(extractionSupplier), RESOLVED); 112 | } 113 | 114 | public static LookupResult createResolved(Function> extractionFunction, Object object) 115 | { 116 | return new LookupResult(ResultAccessor.from(extractionFunction, object), RESOLVED); 117 | } 118 | 119 | public static LookupResult createUnresolved() 120 | { 121 | return new LookupResult(ResultAccessor.from(() -> 122 | { 123 | throw new UnresolvedLookupException(); 124 | }), UNRESOLVED); 125 | } 126 | 127 | public static LookupResult createExceptional(Supplier exceptionSupplier) 128 | { 129 | 130 | return new LookupResult(ResultAccessor.from(() -> 131 | { 132 | throw new RuntimeException(exceptionSupplier.get()); 133 | }), EXCEPTIONAL); 134 | } 135 | 136 | public static LookupResult lazy(Supplier lookupResultSupplier) 137 | { 138 | return new LookupResult(lookupResultSupplier); 139 | } 140 | 141 | public LookupType getLookupType() 142 | { 143 | return lookupType; 144 | } 145 | 146 | public boolean isResolved() 147 | { 148 | return isCertainLookupType(RESOLVED); 149 | } 150 | 151 | public boolean isExceptional() 152 | { 153 | return isCertainLookupType(EXCEPTIONAL); 154 | } 155 | 156 | public boolean isUnresolved() 157 | { 158 | return isCertainLookupType(UNRESOLVED); 159 | } 160 | 161 | public boolean isLazy() 162 | { 163 | return this.getLookupType().equals(LAZY); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/exception/LookupConflictException.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.exception; 2 | 3 | public class LookupConflictException extends RuntimeException 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/exception/RecursiveLookupException.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.exception; 2 | 3 | public class RecursiveLookupException extends RuntimeException 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/exception/RepeatedFieldsException.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.exception; 2 | 3 | public class RepeatedFieldsException extends RuntimeException 4 | { 5 | public RepeatedFieldsException(String message) 6 | { 7 | super(message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/exception/UnresolvedLookupException.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.exception; 2 | 3 | public class UnresolvedLookupException extends RuntimeException 4 | { 5 | public UnresolvedLookupException() 6 | { 7 | } 8 | 9 | public UnresolvedLookupException(String message) 10 | { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/util/AnnotatedTypeReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.util; 2 | 3 | import static java.util.Objects.isNull; 4 | import static java.util.Objects.nonNull; 5 | import static java.util.stream.Collectors.toList; 6 | import static org.apache.commons.lang3.StringUtils.isNotEmpty; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Optional; 14 | import java.util.stream.Stream; 15 | import com.nixsolutions.logging.annotation.LoggableType; 16 | import com.nixsolutions.logging.parameters.loggabletype.AnnotatedObject; 17 | 18 | public class AnnotatedTypeReflectionUtils 19 | { 20 | public static List getClassesToExtract(AnnotatedObject annotatedObject) 21 | { 22 | LoggableType annotation = annotatedObject.getAnnotation(); 23 | if (isNull(annotation)) 24 | { 25 | return Collections.emptyList(); 26 | } 27 | 28 | 29 | Class objectClass = annotatedObject.getObjectClass(); 30 | return (annotation.ignoreParents()) ? Collections.singletonList(objectClass) : getClassesHierarchy(objectClass); 31 | } 32 | 33 | public static List getClassesHierarchy(Class clazz) 34 | { 35 | List classList = new ArrayList<>(); 36 | classList.add(clazz); 37 | Class superclass = clazz.getSuperclass(); 38 | classList.add(superclass); 39 | while (superclass != Object.class) 40 | { 41 | clazz = superclass; 42 | superclass = clazz.getSuperclass(); 43 | if (!superclass.isInterface() && superclass.isAnnotationPresent(LoggableType.class)) 44 | { 45 | classList.add(superclass); 46 | } 47 | } 48 | Collections.reverse(classList); 49 | return classList; 50 | } 51 | 52 | public static Optional getSupplierMethod(Object object) 53 | { 54 | List methods = Stream.of(object.getClass().getDeclaredMethods()) 55 | .filter(method -> method.isAnnotationPresent(LoggableType.extractionMethod.class)) 56 | .collect(toList()); 57 | if (methods.size() > 1) 58 | { 59 | throw new IllegalStateException("There can't be more than one method annotated @LoggableType.extractionMethod"); 60 | } 61 | 62 | 63 | return methods.stream().findFirst(); 64 | } 65 | 66 | public static String getRenamedFieldNameOrDefault(Field field) 67 | { 68 | LoggableType.property annotation = field.getAnnotation(LoggableType.property.class); 69 | if (nonNull(annotation) && isNotEmpty(annotation.name())) 70 | { 71 | return annotation.name(); 72 | } 73 | return field.getName(); 74 | } 75 | 76 | public static boolean isRecursiveLoop(Map> fieldsProcessedBefore, Field currentField) 77 | { 78 | if (isNull(currentField)) 79 | { 80 | return false; 81 | } 82 | 83 | Optional> classes = Optional.ofNullable(fieldsProcessedBefore.get(currentField.getType())); 84 | if (classes.isPresent()) 85 | { 86 | return classes.map(clazzes -> clazzes.contains(currentField.getDeclaringClass())).get(); 87 | } 88 | 89 | return false; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/util/AnnotationLookupConstants.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.util; 2 | 3 | import static org.springframework.util.ReflectionUtils.getField; 4 | import java.lang.reflect.Field; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.function.Function; 8 | import java.util.function.Predicate; 9 | import org.apache.commons.lang3.ClassUtils; 10 | import org.apache.commons.lang3.tuple.Pair; 11 | import com.nixsolutions.logging.annotation.LoggableType; 12 | import com.nixsolutions.logging.parameters.loggabletype.AnnotatedObject; 13 | import com.nixsolutions.logging.parameters.loggabletype.LookupResult; 14 | import com.google.common.collect.ImmutableList; 15 | 16 | public interface AnnotationLookupConstants 17 | { 18 | List PRIMITIVES = ImmutableList.of( 19 | int.class, double.class, 20 | float.class, long.class, 21 | char.class, boolean.class, 22 | String.class); 23 | 24 | Predicate IS_ENUM_TYPE = Enum.class::isAssignableFrom; 25 | 26 | Predicate IS_PRIMITIVE_TYPE = ClassUtils::isPrimitiveOrWrapper; 27 | 28 | Predicate IS_TO_STRING_APPLICABLE_TO_CLASS = clazz -> 29 | IS_ENUM_TYPE.or(IS_PRIMITIVE_TYPE).or(String.class::equals).test(clazz); 30 | 31 | Function> 32 | TO_PROCESSED_FIELDS_METADATA = field -> Pair.of(field.getDeclaringClass(), field.getType()); 33 | 34 | Predicate>> IS_FIELD_COMPLEX = pair -> 35 | pair.getRight().isAnnotated(); 36 | 37 | Function>>> 38 | FIELD_TO_FIELD_OBJ_CURRIED = 39 | obj -> field -> Pair.of(field, AnnotatedObject.createWithAnnotation(getField(field, obj), LoggableType.class)); 40 | 41 | Function THROW_EX_LOOKUP = 42 | ex -> LookupResult.createExceptional(() -> 43 | { 44 | throw ex; 45 | }); 46 | 47 | LookupResult DO_NOTHING_LOOKUP = LookupResult.createResolved(() -> new HashMap<>()); 48 | 49 | String FIELD_NON_EXTRACTABLE_EXCEPTION_MESSAGE = 50 | "Field %s cannot be extracted: No extractor found and toString() is not applicable"; 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/util/AnnotationReflectionLookupUtils.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.util; 2 | 3 | import static com.nixsolutions.logging.LoggingConstants.SINGLE_PROPERTY; 4 | import static com.nixsolutions.logging.common.MapUtils.mergeMaps; 5 | import static com.nixsolutions.logging.parameters.loggabletype.util.AnnotatedTypeReflectionUtils.getClassesToExtract; 6 | import static com.nixsolutions.logging.parameters.loggabletype.util.AnnotatedTypeReflectionUtils 7 | .getRenamedFieldNameOrDefault; 8 | import static com.nixsolutions.logging.parameters.loggabletype.util.AnnotatedTypeReflectionUtils.getSupplierMethod; 9 | import static com.nixsolutions.logging.parameters.loggabletype.util.AnnotatedTypeReflectionUtils.isRecursiveLoop; 10 | import static java.lang.String.format; 11 | import static java.util.Objects.isNull; 12 | import static java.util.stream.Collectors.groupingBy; 13 | import static java.util.stream.Collectors.mapping; 14 | import static java.util.stream.Collectors.toList; 15 | import static java.util.stream.Collectors.toMap; 16 | import java.lang.reflect.Field; 17 | import java.lang.reflect.Method; 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.Collection; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Map.Entry; 26 | import java.util.Optional; 27 | import java.util.Set; 28 | import java.util.function.Function; 29 | import java.util.stream.Stream; 30 | import org.apache.commons.lang3.tuple.Pair; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.stereotype.Component; 33 | import com.nixsolutions.logging.annotation.LoggableType; 34 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 35 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractorFactory; 36 | import com.nixsolutions.logging.parameters.loggabletype.AnnotatedObject; 37 | import com.nixsolutions.logging.parameters.loggabletype.ContextParamsAccessor; 38 | import com.nixsolutions.logging.parameters.loggabletype.ExtractionResolutionStrategy; 39 | import com.nixsolutions.logging.parameters.loggabletype.LookupResult; 40 | import com.nixsolutions.logging.parameters.loggabletype.exception.LookupConflictException; 41 | import com.nixsolutions.logging.parameters.loggabletype.exception.RecursiveLookupException; 42 | import com.nixsolutions.logging.parameters.loggabletype.exception.RepeatedFieldsException; 43 | import com.nixsolutions.logging.parameters.loggabletype.exception.UnresolvedLookupException; 44 | import com.google.common.collect.ImmutableMap; 45 | import com.google.common.collect.Multimap; 46 | import com.google.common.collect.MultimapBuilder; 47 | import com.nixsolutions.logging.common.MapUtils; 48 | 49 | @Component 50 | @SuppressWarnings("unchecked") 51 | public class AnnotationReflectionLookupUtils implements AnnotationLookupConstants 52 | { 53 | 54 | @Autowired 55 | private ContextParamExtractorFactory contextParamExtractorFactory; 56 | 57 | private LookupResult strategyLookupForField(Map> fieldsProcessedBefore, 58 | Pair> fieldObjPair) 59 | { 60 | if (isRecursiveLoop(fieldsProcessedBefore, fieldObjPair.getLeft())) 61 | { 62 | return THROW_EX_LOOKUP.apply(new RecursiveLookupException()); 63 | } 64 | 65 | AnnotatedObject annotatedObject = fieldObjPair.getRight(); 66 | ExtractionResolutionStrategy strategy = Optional.ofNullable(annotatedObject) 67 | .map(AnnotatedObject::getAnnotation) 68 | .map(LoggableType::resolutionStrategy) 69 | .orElse(ExtractionResolutionStrategy.COLLECTOR_FIRST); 70 | 71 | LookupResult collectorLookup = LookupResult.lazy(() -> objectCollectorLookup(fieldsProcessedBefore, 72 | fieldObjPair)); 73 | LookupResult extractorLookup = LookupResult.lazy(() -> extractorLookup(annotatedObject)); 74 | 75 | if (strategy == ExtractionResolutionStrategy.COLLECTOR_FIRST) 76 | { 77 | return LookupUtils.resultingLookup( 78 | collectorLookup, 79 | extractorLookup, 80 | DO_NOTHING_LOOKUP); 81 | } 82 | else if (strategy == ExtractionResolutionStrategy.EXTRACTOR_FIRST) 83 | { 84 | return LookupUtils.resultingLookup( 85 | extractorLookup, 86 | collectorLookup, 87 | DO_NOTHING_LOOKUP); 88 | } 89 | else if (strategy == ExtractionResolutionStrategy.RAISE_EX_ON_CONFLICT) 90 | { 91 | return LookupUtils.resultingLookup(LookupUtils.conflictingLookup( 92 | THROW_EX_LOOKUP.apply(new LookupConflictException()), 93 | collectorLookup, 94 | extractorLookup)); 95 | } 96 | else //strategy = DO_NOTHING 97 | { 98 | return LookupUtils.resultingLookup(LookupUtils.conflictingLookup( 99 | DO_NOTHING_LOOKUP, 100 | collectorLookup, 101 | extractorLookup)); 102 | } 103 | } 104 | 105 | public LookupResult strategyLookupForRootObj(AnnotatedObject annotatedObject) 106 | { 107 | if (IS_TO_STRING_APPLICABLE_TO_CLASS.test(annotatedObject.getObjectClass())) 108 | { 109 | return LookupResult.createResolved( 110 | () -> Collections.singletonMap(SINGLE_PROPERTY, annotatedObject.getObject())); 111 | } 112 | 113 | return LookupResult.lazy(() -> strategyLookupForField(new HashMap<>(), Pair.of(null, annotatedObject))); 114 | } 115 | 116 | private LookupResult extractorLookup(AnnotatedObject annotatedObject) 117 | { 118 | ContextParamExtractor extractor = contextParamExtractorFactory 119 | .getExtractorByClass(annotatedObject.getObjectClass()); 120 | if (isNull(extractor)) 121 | { 122 | return LookupResult.createUnresolved(); 123 | } 124 | 125 | return LookupResult.createResolved(extractor::extractParams, annotatedObject.getObject()); 126 | } 127 | 128 | private LookupResult objectCollectorLookup(Map> fieldsProcessedBefore, Pair> fieldObjPair) 130 | { 131 | AnnotatedObject annotatedObject = fieldObjPair.getRight(); 132 | 133 | return LookupUtils.resultingLookup( 134 | accessorMethodLookup(annotatedObject), 135 | annotatedMethodLookup(annotatedObject), 136 | collectorLookup(fieldsProcessedBefore, fieldObjPair)); 137 | } 138 | 139 | private LookupResult collectorLookup(Map> fieldsProcessedBefore, Pair> fieldObjPair) 141 | { 142 | List>> allFields = getAnnotatedFieldObjPairs(fieldObjPair.getRight()); 143 | LookupResult eligibleFieldsContextParamLookup = getCompositeFieldsContextParamLookup(fieldsProcessedBefore, 144 | allFields); 145 | LookupResult notEligibleFieldsContextParamLookup = plainFieldsContextParamLookup(allFields); 146 | 147 | LookupResult errorLookup = LookupUtils.errorLookup(notEligibleFieldsContextParamLookup, 148 | eligibleFieldsContextParamLookup); 149 | 150 | if (errorLookup != null) 151 | { 152 | return errorLookup; 153 | } 154 | 155 | Map simpleContextParams = notEligibleFieldsContextParamLookup.executeForResult(); 156 | Map complexContextParams = eligibleFieldsContextParamLookup.executeForResult(); 157 | 158 | return LookupResult.createResolved(() -> 159 | MapUtils.mergeMaps(simpleContextParams, complexContextParams)); 160 | } 161 | 162 | private Map mergeContextParamMaps(Map simpleContextParams, 163 | Map complexContextParams, 164 | Field complexField) 165 | { 166 | if (isNull(complexField)) 167 | { 168 | return MapUtils.mergeMaps(simpleContextParams, complexContextParams); 169 | } 170 | 171 | Map finalResult = ImmutableMap.builder() 172 | .putAll(simpleContextParams) 173 | .put(complexField.getName(), complexContextParams).build(); 174 | 175 | return finalResult; 176 | } 177 | 178 | //TODO CLEANUP/REFACTOR 179 | private LookupResult getCompositeFieldsContextParamLookup(Map> fieldsProcessedBefore, 180 | List>> allFields) 181 | { 182 | List>> compositeFields = allFields.stream() 183 | .filter(IS_FIELD_COMPLEX) 184 | .collect(toList()); 185 | 186 | rejectErrorOnDuplicatingFields(compositeFields); 187 | 188 | if (compositeFields.isEmpty()) 189 | { 190 | return DO_NOTHING_LOOKUP; 191 | } 192 | 193 | fieldsProcessedBefore.putAll(getClassFieldRelationMetadata(compositeFields)); 194 | 195 | List compositeFieldsLookups = new ArrayList<>(); 196 | 197 | for (Pair> fieldObjPair : compositeFields) 198 | { 199 | LookupResult lookupResultToCheck = 200 | strategyLookupForField(fieldsProcessedBefore, fieldObjPair); 201 | 202 | if (lookupResultToCheck.isExceptional()) 203 | { 204 | return lookupResultToCheck; 205 | } 206 | 207 | LookupResult adjustedToFieldResult = LookupResult.createResolved(() -> ImmutableMap.of( 208 | getRenamedFieldNameOrDefault(fieldObjPair.getLeft()), 209 | lookupResultToCheck.executeForResult())); 210 | compositeFieldsLookups.add(adjustedToFieldResult); 211 | } 212 | 213 | return LookupResult.lazy(() -> getMergedLookupResult(compositeFieldsLookups)); 214 | } 215 | 216 | private Pair> toLookupObjFromCompositeFieldObjPair( 217 | Map> fieldsProcessedBefore, 218 | Pair> fieldObjPair) 219 | { 220 | return Pair.of(strategyLookupForField(fieldsProcessedBefore, fieldObjPair), fieldObjPair.getRight()); 221 | } 222 | 223 | private LookupResult getMergedLookupResult(List compositeFields) 224 | { 225 | return LookupResult.createResolved( 226 | () -> compositeFields.stream() 227 | .map(LookupResult::executeForResult) 228 | .map(Map::entrySet) 229 | .flatMap(Set::stream) 230 | .collect(toMap(Entry::getKey, Entry::getValue))); 231 | } 232 | 233 | private Map> getClassFieldRelationMetadata(List>> 234 | compositeFields) 235 | { 236 | return compositeFields.stream() 237 | .map(Pair::getLeft) 238 | .map(TO_PROCESSED_FIELDS_METADATA) 239 | .collect(groupingBy(Pair::getLeft, mapping(Pair::getRight, toList()))); 240 | } 241 | 242 | private List>> getAnnotatedFieldObjPairs(AnnotatedObject 243 | annotatedObject) 244 | { 245 | Function>> transformFn = 246 | FIELD_TO_FIELD_OBJ_CURRIED.apply(annotatedObject.getObject()); 247 | List classes = getClassesToExtract(annotatedObject); 248 | return classes.stream() 249 | .map(Class::getDeclaredFields) 250 | .flatMap(Arrays::stream) 251 | .filter(field -> field.isAnnotationPresent(LoggableType.property.class)) 252 | .map(transformFn) 253 | .collect(toList()); 254 | } 255 | 256 | private LookupResult plainFieldsContextParamLookup(List>> allFields) 257 | { 258 | try 259 | { 260 | Map contextParamsForNotEligibleFields = collectContextParamsForPlainFields(allFields); 261 | return LookupResult.createResolved(() -> contextParamsForNotEligibleFields); 262 | } catch (Exception e) 263 | { 264 | return LookupResult.createExceptional(() -> e); 265 | } 266 | } 267 | 268 | private Map collectContextParamsForPlainFields(List>> 269 | allFields) 270 | { 271 | List>> plainFields = allFields.stream() 272 | .filter(IS_FIELD_COMPLEX.negate()) 273 | .collect(toList()); 274 | 275 | rejectErrorOnDuplicatingFields(plainFields); 276 | 277 | return plainFields.stream() 278 | .flatMap(this::toFieldNameValuePair) 279 | .collect(toMap(Entry::getKey, Entry::getValue)); 280 | } 281 | 282 | private void rejectErrorOnDuplicatingFields(List>> fields) 283 | { 284 | Multimap fieldClassesCollision = MultimapBuilder 285 | .hashKeys() 286 | .arrayListValues() 287 | .build(); 288 | 289 | fields.stream() 290 | .map(pair -> pair.getKey()) 291 | .forEach(field -> 292 | fieldClassesCollision.put( 293 | getRenamedFieldNameOrDefault(field), 294 | field.getDeclaringClass().getName())); 295 | 296 | Map> repeatedFields = fieldClassesCollision.asMap().entrySet().stream() 297 | .filter(entry -> entry.getValue().size() > 1) 298 | .collect(toMap(Entry::getKey, Entry::getValue)); 299 | 300 | if (repeatedFields.size() >= 1) 301 | { 302 | throw new RepeatedFieldsException(repeatedFields.toString()); 303 | } 304 | 305 | } 306 | 307 | private Stream> toFieldNameValuePair(Pair> fieldObjectPair) 308 | { 309 | Field field = fieldObjectPair.getLeft(); 310 | Object value = fieldObjectPair.getRight().getObject(); 311 | String fieldName = getRenamedFieldNameOrDefault(field); 312 | 313 | if (IS_TO_STRING_APPLICABLE_TO_CLASS.test(field.getType())) 314 | { 315 | return Stream.of(Pair.of(fieldName, value)); 316 | } 317 | 318 | LookupResult lookupResult = extractorLookup(fieldObjectPair.getRight()); 319 | if (lookupResult.isResolved()) 320 | { 321 | return lookupResult.executeForResult().entrySet().stream(); 322 | } 323 | 324 | throw new UnresolvedLookupException(format(FIELD_NON_EXTRACTABLE_EXCEPTION_MESSAGE, fieldName)); 325 | } 326 | 327 | private LookupResult accessorMethodLookup(AnnotatedObject annotatedObject) 328 | { 329 | Object object = annotatedObject.getObject(); 330 | if (object instanceof ContextParamsAccessor) 331 | { 332 | return LookupResult.createResolved(((ContextParamsAccessor) object)::extractParams); 333 | } 334 | return LookupResult.createUnresolved(); 335 | } 336 | 337 | private LookupResult annotatedMethodLookup(AnnotatedObject annotatedObject) 338 | { 339 | try 340 | { 341 | Object object = annotatedObject.getObject(); 342 | Optional supplierMethod = getSupplierMethod(object); 343 | if (supplierMethod.isPresent()) 344 | { 345 | Map invocationResult = (Map) supplierMethod.get().invoke(object); 346 | return LookupResult.createResolved(() -> invocationResult); 347 | } 348 | } catch (Exception ex) 349 | { 350 | return LookupResult.createExceptional(() -> ex); 351 | } 352 | return LookupResult.createUnresolved(); 353 | } 354 | 355 | } -------------------------------------------------------------------------------- /src/main/java/com/nixsolutions/logging/parameters/loggabletype/util/LookupUtils.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.util; 2 | 3 | import static com.nixsolutions.logging.parameters.loggabletype.LookupResult.LookupType.EXCEPTIONAL; 4 | import static com.nixsolutions.logging.parameters.loggabletype.LookupResult.LookupType.RESOLVED; 5 | import static java.util.Objects.nonNull; 6 | import static java.util.stream.Collectors.toList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import com.nixsolutions.logging.parameters.loggabletype.LookupResult; 10 | 11 | public class LookupUtils 12 | { 13 | 14 | public static LookupResult resultingLookup(LookupResult... lookupOrder) 15 | { 16 | LookupResult errorLookup = errorLookup(lookupOrder); 17 | if (nonNull(errorLookup)) 18 | { 19 | return errorLookup; 20 | } 21 | 22 | return firstSpecificLookup(RESOLVED, lookupOrder); 23 | } 24 | 25 | public static LookupResult errorLookup(LookupResult... lookupOrder) 26 | { 27 | return firstSpecificLookup(EXCEPTIONAL, lookupOrder); 28 | } 29 | 30 | public static LookupResult firstSpecificLookup(LookupResult.LookupType lookupType, LookupResult... lookupOrder) 31 | { 32 | if (lookupOrder.length == 1) 33 | { 34 | return lookupOrder[0]; 35 | } 36 | return Arrays.stream(lookupOrder) 37 | .filter(lookupResult -> lookupResult.isCertainLookupType(lookupType)) 38 | .findFirst() 39 | .orElse(null); 40 | } 41 | 42 | public static LookupResult conflictingLookup(LookupResult onConflict, LookupResult... results) 43 | { 44 | List lookupResults = Arrays.stream(results) 45 | .filter(LookupResult::isResolved) 46 | .collect(toList()); 47 | 48 | if (lookupResults.size() > 1) 49 | { 50 | return onConflict; 51 | } 52 | 53 | return lookupResults.get(0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/LogContextDefaultTest.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging; 2 | 3 | import static java.text.MessageFormat.format; 4 | import static org.apache.commons.lang3.StringUtils.EMPTY; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import java.io.Serializable; 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | import java.util.stream.Stream; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.junit.jupiter.api.Assertions; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.TestInstance; 17 | import org.junit.jupiter.params.ParameterizedTest; 18 | import org.junit.jupiter.params.provider.Arguments; 19 | import org.junit.jupiter.params.provider.MethodSource; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 24 | class LogContextDefaultTest 25 | { 26 | private static final Logger LOG = LoggerFactory.getLogger(LogContextDefaultTest.class); 27 | private static final String MESSAGE = "message"; 28 | private static final Long QUOTE_ID = 47777L; 29 | private static final String QUOTE_ID_STRING = "47777"; 30 | private static final String QUOTE_NAME_KEY = "quoteName"; 31 | private static final String QUOTE_NAME_VALUE = "UberQuote"; 32 | private static final String QUOTE_STATUS_KEY = "quoteStatus"; 33 | private static final String QUOTE_STATUS_VALUE = "Jumping"; 34 | 35 | private LogContext logContextDefault = new LogContextDefault(); 36 | 37 | private static final String CONTEXT_PREFIX = "ctx:{"; 38 | private static final String CONTEXT_SUFFIX = "}"; 39 | 40 | @ParameterizedTest(name = "Should {0}.") 41 | @MethodSource("argumentsForShouldAddInfoAndGetMessage") 42 | void shouldAddInfoAndGetMessage( 43 | String testCaseName, 44 | String message, 45 | Map quoteInfo, 46 | Long key, 47 | String expectedResult) 48 | { 49 | //given 50 | 51 | //when 52 | String actual = logContextDefault.get(message, key, quoteInfo); 53 | 54 | //then 55 | Assertions.assertEquals(expectedResult, actual); 56 | } 57 | 58 | private Stream argumentsForShouldAddInfoAndGetMessage() 59 | { 60 | return Stream.of( 61 | Arguments.of( 62 | "return formatted message when quoteInfo map is empty", 63 | MESSAGE, 64 | new HashMap<>(), 65 | QUOTE_ID, 66 | createMessageWithContextPattern(MESSAGE, format("key={0}", QUOTE_ID_STRING)) 67 | ), 68 | Arguments.of( 69 | "return formatted message when quoteInfo map is null", 70 | MESSAGE, 71 | null, 72 | QUOTE_ID, 73 | createMessageWithContextPattern(MESSAGE, format("key={0}", QUOTE_ID_STRING)) 74 | ), 75 | Arguments.of( 76 | "return formatted message when quoteInfo map is immutable and prefilled with some values", 77 | MESSAGE, 78 | createMap(QUOTE_NAME_KEY, QUOTE_NAME_VALUE, QUOTE_STATUS_KEY, QUOTE_STATUS_VALUE), 79 | QUOTE_ID, 80 | createMessageWithContextPattern(MESSAGE, 81 | format("key={0}, {1}={2}, {3}={4}", QUOTE_ID_STRING, QUOTE_NAME_KEY, QUOTE_NAME_VALUE, 82 | QUOTE_STATUS_KEY, QUOTE_STATUS_VALUE)) 83 | ), 84 | Arguments.of( 85 | "return formatted message with proper order when quoteInfo prefilled with values. Info by key first then " + 86 | "additional values", 87 | MESSAGE, 88 | createMap(QUOTE_NAME_KEY, QUOTE_NAME_VALUE, QUOTE_STATUS_KEY, QUOTE_STATUS_VALUE), 89 | QUOTE_ID, 90 | createMessageWithContextPattern(MESSAGE, 91 | format("key={0}, {1}={2}, {3}={4}", QUOTE_ID_STRING, QUOTE_NAME_KEY, QUOTE_NAME_VALUE, 92 | QUOTE_STATUS_KEY, QUOTE_STATUS_VALUE)) 93 | ), 94 | Arguments.of( 95 | "return formatted message when key is null", 96 | MESSAGE, 97 | Collections.singletonMap(QUOTE_NAME_KEY, QUOTE_NAME_VALUE), 98 | null, 99 | createMessageWithContextPattern(MESSAGE, format("{0}={1}", QUOTE_NAME_KEY, QUOTE_NAME_VALUE)) 100 | ), 101 | Arguments.of( 102 | "return formatted message is null", 103 | null, 104 | new HashMap<>(), 105 | QUOTE_ID, 106 | createMessageWithContextPattern(EMPTY, format("key={0}", QUOTE_ID_STRING)) 107 | ), 108 | Arguments.of( 109 | "return formatted message is blank", 110 | " ", 111 | new HashMap<>(), 112 | QUOTE_ID, 113 | createMessageWithContextPattern(EMPTY, format("key={0}", QUOTE_ID_STRING)) 114 | ) 115 | ); 116 | } 117 | 118 | private Map createMap(String... args) 119 | { 120 | Map map = new LinkedHashMap<>(); 121 | for (int i = 0; i < args.length; i = i + 2) 122 | { 123 | map.put(args[i], args[i + 1]); 124 | } 125 | Map nestedMap = new LinkedHashMap<>(); 126 | nestedMap.put("something", "good"); 127 | nestedMap.put("nothing", "bad"); 128 | // map.put("nes", ""); 129 | return map; 130 | } 131 | 132 | private String createMessageWithContextPattern(String message, String params) 133 | { 134 | String contextPrefix = StringUtils.isNotBlank(message) ? ". " + CONTEXT_PREFIX : CONTEXT_PREFIX; 135 | 136 | return StringUtils.join(message, contextPrefix, params, CONTEXT_SUFFIX); 137 | } 138 | 139 | private static class ClassWithCustomToString 140 | { 141 | @Override 142 | public String toString() 143 | { 144 | return "customToString"; 145 | } 146 | } 147 | 148 | @Test 149 | void testStream() 150 | { 151 | Optional first = Stream.of(new Dummy(false), 152 | new Dummy(true), 153 | new Dummy(false), 154 | new Dummy(false), 155 | new Dummy(true), 156 | new Dummy(false)).filter(Dummy::isResolved).findFirst(); 157 | 158 | } 159 | 160 | @Test 161 | void testInterfaceCheck() 162 | { 163 | DummyBase dummyBase = new DummyBase(); 164 | DummyDerived dummyDerived = new DummyDerived(); 165 | 166 | assertTrue(dummyBase instanceof Serializable); 167 | assertTrue(dummyDerived instanceof Serializable); 168 | } 169 | 170 | private static class DummyBase implements Serializable 171 | {} 172 | 173 | private static class DummyDerived extends DummyBase 174 | {} 175 | 176 | private static class Dummy 177 | { 178 | private static int counter_gl =0; 179 | boolean resolved; 180 | int counter; 181 | 182 | public Dummy(boolean resolved) 183 | { 184 | counter_gl++; 185 | counter = counter_gl; 186 | this.resolved = resolved; 187 | } 188 | 189 | public boolean isResolved() 190 | { 191 | LOG.error("Resolving..."+ counter); 192 | return resolved; 193 | } 194 | } 195 | 196 | private static class DummyClass 197 | { 198 | private String name; 199 | private Long value; 200 | 201 | DummyClass(String name, Long value) 202 | { 203 | this.name = name; 204 | this.value = value; 205 | } 206 | 207 | public String getName() 208 | { 209 | return name; 210 | } 211 | 212 | public Long getValue() 213 | { 214 | return value; 215 | } 216 | 217 | @Override 218 | public String toString() 219 | { 220 | return "dummyClassToString"; 221 | } 222 | } 223 | } -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/advice/LoggingAdviceTest.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice; 2 | 3 | import static com.nixsolutions.logging.advice.LoggingResultHelper.PARAM_LONG; 4 | import static com.nixsolutions.logging.advice.LoggingResultHelper.PARAM_STR; 5 | import static com.nixsolutions.logging.advice.LoggingResultHelper.supposeThat; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.PrintStream; 8 | import java.util.stream.Stream; 9 | import org.junit.jupiter.api.AfterEach; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.BeforeAll; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | import org.junit.jupiter.api.TestInstance; 15 | import org.junit.jupiter.api.extension.ExtendWith; 16 | import org.junit.jupiter.params.ParameterizedTest; 17 | import org.junit.jupiter.params.provider.Arguments; 18 | import org.junit.jupiter.params.provider.MethodSource; 19 | import org.slf4j.LoggerFactory; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 24 | import org.springframework.context.annotation.Primary; 25 | import org.springframework.test.context.ContextConfiguration; 26 | import org.springframework.test.context.junit.jupiter.SpringExtension; 27 | import com.fasterxml.jackson.databind.JsonNode; 28 | import com.fasterxml.jackson.databind.ObjectMapper; 29 | import com.nixsolutions.logging.LogContext; 30 | import com.nixsolutions.logging.LogContextJson; 31 | import com.nixsolutions.logging.advice.pojo.Pojo; 32 | import com.nixsolutions.logging.configuration.LoggingConfiguration; 33 | import com.nixsolutions.logging.integration.SampleService; 34 | 35 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 36 | @Configuration 37 | @ContextConfiguration(classes = {LoggingAdviceTest.class, LoggingConfiguration.class}) 38 | @ExtendWith(SpringExtension.class) 39 | @EnableAspectJAutoProxy(proxyTargetClass = true) 40 | public class LoggingAdviceTest 41 | { 42 | private ObjectMapper objectMapper = new ObjectMapper(); 43 | 44 | private PrintStream stdout = System.out; 45 | private PrintStream stderr = System.err; 46 | 47 | private ByteArrayOutputStream outStream; 48 | 49 | @Autowired 50 | SampleService sampleService; 51 | 52 | @BeforeAll 53 | public static void beforeAll() 54 | { 55 | LoggerFactory.getLogger(LoggingAdviceTest.class).debug("{\"status\": \"TEST STARTED\"}"); 56 | } 57 | 58 | @BeforeEach 59 | public void beforeTest() 60 | { 61 | outStream = new ByteArrayOutputStream(); 62 | 63 | System.setOut(new PrintStream(outStream)); 64 | } 65 | 66 | @AfterEach 67 | public void afterTest() 68 | { 69 | System.setOut(stdout); 70 | System.setErr(stderr); 71 | } 72 | 73 | @ParameterizedTest(name = "Should {0}") 74 | @MethodSource("argsForTestEntryLogging") 75 | public void testEntryLogging(String name, Runnable methodRun, String fileName) throws Exception 76 | { 77 | //when 78 | methodRun.run(); 79 | 80 | //then 81 | JsonNode expected = objectMapper 82 | .readTree(LoggingAdviceTest.class.getResourceAsStream("entry/" + fileName)); 83 | 84 | supposeThat() 85 | .givenSource(outStream) 86 | .fromCtx() 87 | .shouldBeEqualTo(expected); 88 | } 89 | 90 | private Stream argsForTestEntryLogging() 91 | { 92 | return Stream.of( 93 | Arguments.of( 94 | "not log entry params if not specified", 95 | (Runnable) () -> sampleService.method(), 96 | "emptyCtx.json" 97 | ), 98 | Arguments.of( 99 | "log single string param", 100 | (Runnable) () -> sampleService.method(PARAM_STR), 101 | "strParam.json" 102 | ), 103 | Arguments.of( 104 | "log renamed string parameter", 105 | (Runnable) () -> sampleService.methodWithRenamedParam(PARAM_STR), 106 | "renamedParam.json" 107 | ), 108 | Arguments.of( 109 | "log multiple parameters", 110 | (Runnable) () -> sampleService.methodWithMultipleParams(PARAM_STR, PARAM_LONG), 111 | "multipleParams.json" 112 | ), 113 | Arguments.of( 114 | "log complex parameter", 115 | (Runnable) () -> sampleService.methodWithComplexParam(new Pojo()), 116 | "complexParam.json" 117 | ) 118 | ); 119 | } 120 | 121 | @ParameterizedTest(name = "Should {0}") 122 | @MethodSource("argsForTestExectimeLogging") 123 | public void testExectimeLogging(String name, Runnable methodRun, String fileName) throws Exception 124 | { 125 | //when 126 | methodRun.run(); 127 | 128 | //then 129 | JsonNode expected = objectMapper 130 | .readTree(LoggingAdviceTest.class.getResourceAsStream("exectime/" + fileName)); 131 | 132 | supposeThat() 133 | .givenSource(outStream) 134 | .fromCtx() 135 | .propertyValueIgnored("timeLoggingContext.duration") 136 | .shouldBeEqualTo(expected); 137 | } 138 | 139 | private Stream argsForTestExectimeLogging() 140 | { 141 | return Stream.of( 142 | Arguments.of( 143 | "log exec time if specified", 144 | (Runnable) () -> sampleService.methodWihExecTimeLogging(), 145 | "simpleTimeLogging.json" 146 | ), 147 | Arguments.of( 148 | "log exec time with adjusted time unit", 149 | (Runnable) () -> sampleService.methodWithExectimeLoggingAndOtherTimeUnit(), 150 | "changedTimeUnit.json" 151 | ), 152 | Arguments.of( 153 | "log exec time with adjusted task name", 154 | (Runnable) () -> sampleService.methodWithExectimeLoggingAndOtherTaskName(), 155 | "adjustedTaskName.json" 156 | ), 157 | Arguments.of( 158 | "log exec time with human readable task name", 159 | (Runnable) () -> sampleService.methodWithExectimeLoggingAndHumanReadableTaskName(), 160 | "humanReadableTaskName.json" 161 | ) 162 | ); 163 | } 164 | 165 | @Test 166 | public void shouldLogTime() throws Exception 167 | { 168 | //when 169 | sampleService.methodWihExecTimeLogging(); 170 | 171 | //then 172 | long duration = new ObjectMapper() 173 | .readTree(outStream.toString()) 174 | .get("context") 175 | .get("ctx") 176 | .get("timeLoggingContext") 177 | .get("duration") 178 | .longValue(); 179 | 180 | 181 | Assertions.assertTrue(duration >= 300L); 182 | } 183 | 184 | @ParameterizedTest(name = "Should {0}") 185 | @MethodSource("argsForTestExitLogging") 186 | public void testExitLogging(String name, Runnable methodRun, String fileName) throws Exception 187 | { 188 | //when 189 | methodRun.run(); 190 | 191 | //then 192 | JsonNode expected = objectMapper 193 | .readTree(LoggingAdviceTest.class.getResourceAsStream("exit/" + fileName)); 194 | 195 | supposeThat() 196 | .givenSource(outStream) 197 | .fromCtx() 198 | .shouldBeEqualTo(expected); 199 | } 200 | 201 | private Stream argsForTestExitLogging() 202 | { 203 | return Stream.of( 204 | Arguments.of( 205 | "log string return param", 206 | (Runnable) () -> sampleService.methodWithStrReturn(), 207 | "strReturnParam.json" 208 | ), 209 | Arguments.of( 210 | "log pojo return param", 211 | (Runnable) () -> sampleService.methodWithPojoReturn(), 212 | "pojoReturnParam.json" 213 | ), 214 | Arguments.of( 215 | "log void return param", 216 | (Runnable) () -> sampleService.methodWithVoidReturn(), 217 | "voidReturn.json" 218 | ) 219 | ); 220 | } 221 | 222 | @Test 223 | public void shouldLogExceptionTerminatedMethod() throws Exception 224 | { 225 | //when 226 | try 227 | { 228 | sampleService.methodTerminatedWithException(); 229 | } 230 | catch (Exception e) 231 | { 232 | 233 | } 234 | 235 | //then 236 | JsonNode actual = objectMapper.readTree(outStream.toString()); 237 | 238 | Assertions.assertTrue(actual.get("exception").asText().length() > 0); 239 | } 240 | 241 | @Primary 242 | @Bean 243 | public LogContext logContextJson() 244 | { 245 | return new LogContextJson(); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/advice/LoggingResultHelper.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.util.ArrayList; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.Queue; 8 | import org.junit.jupiter.api.Assertions; 9 | import com.fasterxml.jackson.databind.JsonNode; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.fasterxml.jackson.databind.node.ObjectNode; 12 | 13 | public class LoggingResultHelper 14 | { 15 | public static final String PARAM_STR = "STR_PARAM"; 16 | public static final Long PARAM_LONG = 1L; 17 | 18 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 19 | 20 | public static class JsonLogAssertion 21 | { 22 | private ByteArrayOutputStream baos; 23 | private Queue nestedProperties = new LinkedList<>(); 24 | private List propertyValuesIgnored = new ArrayList<>(); 25 | 26 | public JsonLogAssertion givenSource(ByteArrayOutputStream byteArrayOutputStream) 27 | { 28 | this.baos = byteArrayOutputStream; 29 | return this; 30 | } 31 | 32 | public JsonLogAssertion fromCtx() 33 | { 34 | return fromProperty("context").fromProperty("ctx"); 35 | } 36 | 37 | public JsonLogAssertion fromProperty(String propertyName) 38 | { 39 | nestedProperties.offer(propertyName); 40 | return this; 41 | } 42 | 43 | public JsonLogAssertion propertyValueIgnored(String property) 44 | { 45 | propertyValuesIgnored.add(property); 46 | return this; 47 | } 48 | 49 | public void shouldBeEqualTo(JsonNode jsonNode) 50 | { 51 | try 52 | { 53 | JsonNode nodeToTraverse = OBJECT_MAPPER.readTree(baos.toString()); 54 | 55 | for (String property : nestedProperties) 56 | { 57 | nodeToTraverse = nodeToTraverse.get(property); 58 | } 59 | 60 | for (String property : propertyValuesIgnored) 61 | { 62 | nullifyField(jsonNode, property); 63 | nullifyField(nodeToTraverse, property); 64 | } 65 | 66 | Assertions.assertEquals(jsonNode, nodeToTraverse); 67 | 68 | } 69 | catch (Exception ex) 70 | { 71 | throw new RuntimeException(ex); 72 | } 73 | } 74 | 75 | private void nullifyField(JsonNode node, String property) 76 | { 77 | String[] props = property.split("\\."); 78 | 79 | ObjectNode objectNode = new ObjectNode(null, null); 80 | 81 | for (int i = 0; i < props.length - 1; i++) 82 | { 83 | objectNode = (ObjectNode) node.get(props[i]); 84 | } 85 | 86 | objectNode.put(props[props.length - 1], ""); 87 | 88 | } 89 | } 90 | 91 | public static JsonLogAssertion supposeThat() 92 | { 93 | return new JsonLogAssertion(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/advice/pojo/EnumType.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.pojo; 2 | 3 | public enum EnumType 4 | { 5 | ENUM_PARAM 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/advice/pojo/Pojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.advice.pojo; 2 | 3 | import static com.nixsolutions.logging.advice.LoggingResultHelper.PARAM_LONG; 4 | import static com.nixsolutions.logging.advice.LoggingResultHelper.PARAM_STR; 5 | import com.nixsolutions.logging.annotation.LoggableType; 6 | 7 | @LoggableType 8 | public class Pojo 9 | { 10 | @LoggableType.property 11 | public String strParam = PARAM_STR; 12 | @LoggableType.property 13 | public Long longParam = PARAM_LONG; 14 | @LoggableType.property 15 | public EnumType enumParam = EnumType.ENUM_PARAM; 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/integration/SampleService.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.integration; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import org.springframework.stereotype.Component; 5 | import com.nixsolutions.logging.advice.pojo.Pojo; 6 | import com.nixsolutions.logging.annotation.ContextParam; 7 | import com.nixsolutions.logging.annotation.Log; 8 | 9 | @Component 10 | public class SampleService 11 | { 12 | 13 | 14 | private void timeConsuming() 15 | { 16 | try 17 | { 18 | Thread.sleep(300); 19 | } 20 | catch (InterruptedException e) 21 | { 22 | e.printStackTrace(); 23 | } 24 | } 25 | 26 | //Entry 27 | 28 | @Log 29 | @Log.entry 30 | public void method() 31 | { 32 | } 33 | 34 | @Log 35 | @Log.entry 36 | public void method(@ContextParam String strParam) 37 | { 38 | 39 | } 40 | 41 | @Log 42 | @Log.entry 43 | public void methodWithRenamedParam(@ContextParam("renamedStrParam") String strParam) 44 | { 45 | 46 | } 47 | 48 | @Log 49 | @Log.entry 50 | public void methodWithMultipleParams(@ContextParam String strParam, 51 | @ContextParam Long longParam) 52 | { 53 | 54 | } 55 | 56 | @Log 57 | @Log.entry 58 | public void methodWithComplexParam(@ContextParam Pojo complexParam) 59 | { 60 | 61 | } 62 | 63 | //Exec time 64 | 65 | @Log 66 | @Log.exectime 67 | public void methodWihExecTimeLogging() 68 | { 69 | timeConsuming(); 70 | } 71 | 72 | @Log 73 | @Log.exectime(timeUnit = TimeUnit.MICROSECONDS) 74 | public void methodWithExectimeLoggingAndOtherTimeUnit() 75 | { 76 | 77 | } 78 | 79 | @Log 80 | @Log.exectime(taskName = "newTaskName") 81 | public void methodWithExectimeLoggingAndOtherTaskName() 82 | { 83 | 84 | } 85 | 86 | @Log 87 | @Log.exectime(taskName = "Human readable task name") 88 | public void methodWithExectimeLoggingAndHumanReadableTaskName() 89 | { 90 | 91 | } 92 | 93 | //Exit 94 | 95 | @Log 96 | @Log.exit 97 | public String methodWithStrReturn() 98 | { 99 | return "RETURN_STR"; 100 | } 101 | 102 | @Log 103 | @Log.exit 104 | public Pojo methodWithPojoReturn() 105 | { 106 | return new Pojo(); 107 | } 108 | 109 | @Log 110 | @Log.exit 111 | public void methodWithVoidReturn() 112 | { 113 | } 114 | 115 | @Log 116 | @Log.exit 117 | public void methodTerminatedWithException() 118 | { 119 | throw new RuntimeException(); 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/AnnotationReflectionLookupUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype; 2 | 3 | import static com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS; 4 | import java.io.IOException; 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.util.Optional; 7 | import java.util.stream.Stream; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.TestInstance; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.junit.jupiter.params.ParameterizedTest; 12 | import org.junit.jupiter.params.provider.Arguments; 13 | import org.junit.jupiter.params.provider.MethodSource; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.context.annotation.ComponentScan; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.test.context.ContextConfiguration; 18 | import org.springframework.test.context.junit.jupiter.SpringExtension; 19 | import com.nixsolutions.logging.annotation.LoggableType; 20 | import com.nixsolutions.logging.configuration.ContextExtractorFactoryConfiguration; 21 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 22 | import com.nixsolutions.logging.parameters.loggabletype.cases.accessorinterface.AccessorInterfacePojo; 23 | import com.nixsolutions.logging.parameters.loggabletype.cases.annotatedmethod.AnnotatedMethodPojo; 24 | import com.nixsolutions.logging.parameters.loggabletype.cases.annotatedmethodfails.AnnotatedMEthodFailsPojo; 25 | import com.nixsolutions.logging.parameters.loggabletype.cases.caseinheritance.ChildPojo; 26 | import com.nixsolutions.logging.parameters.loggabletype.cases.conflictinglookup.ConflictingLookupPojo; 27 | import com.nixsolutions.logging.parameters.loggabletype.cases.donothinglookup.DoNothingLookupPojo; 28 | import com.nixsolutions.logging.parameters.loggabletype.cases.emptypojo.Empty; 29 | import com.nixsolutions.logging.parameters.loggabletype.cases.enumtypefield.PojoWithEnumField; 30 | import com.nixsolutions.logging.parameters.loggabletype.cases.multipleannotatedmethods.MultipleAnnotatedMethodsPojo; 31 | import com.nixsolutions.logging.parameters.loggabletype.cases.nestedcollector.Pojo; 32 | import com.nixsolutions.logging.parameters.loggabletype.cases.nestedextractor.PojoWithNestedPojo; 33 | import com.nixsolutions.logging.parameters.loggabletype.cases.notannotatedfields.NotAnnotatedFieldsPojo; 34 | import com.nixsolutions.logging.parameters.loggabletype.cases.recursivefail.RecursiveLoopPojo1; 35 | import com.nixsolutions.logging.parameters.loggabletype.cases.renamedcomplexfield.ComplexFieldRenamedPojo; 36 | import com.nixsolutions.logging.parameters.loggabletype.cases.renamedfield.RenamedFieldPojo; 37 | import com.nixsolutions.logging.parameters.loggabletype.cases.repeatedfieldnames.RepeatedFieldnamesPojo; 38 | import com.nixsolutions.logging.parameters.loggabletype.cases.simpleextractor.SimpleExtractorPojo; 39 | import com.nixsolutions.logging.parameters.loggabletype.cases.unextractablefield.PojoWithUnextractableField; 40 | import com.nixsolutions.logging.parameters.loggabletype.exception.LookupConflictException; 41 | import com.nixsolutions.logging.parameters.loggabletype.exception.RecursiveLookupException; 42 | import com.nixsolutions.logging.parameters.loggabletype.exception.RepeatedFieldsException; 43 | import com.nixsolutions.logging.parameters.loggabletype.exception.UnresolvedLookupException; 44 | import com.nixsolutions.logging.parameters.loggabletype.util.AnnotationReflectionLookupUtils; 45 | import com.fasterxml.jackson.databind.JsonNode; 46 | import com.fasterxml.jackson.databind.ObjectMapper; 47 | 48 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 49 | @Configuration 50 | @ContextConfiguration(classes = {AnnotationReflectionLookupUtilsTest.class, ContextExtractorFactoryConfiguration.class}) 51 | @ComponentScan("com.nixsolutions.logging.parameters") 52 | @ExtendWith(SpringExtension.class) 53 | public class AnnotationReflectionLookupUtilsTest 54 | { 55 | @Autowired 56 | private AnnotationReflectionLookupUtils reflectionLookupUtils; 57 | 58 | private ObjectMapper objectMapper = new ObjectMapper() 59 | .configure(FAIL_ON_EMPTY_BEANS, false); 60 | 61 | private static final SimpleExtractorPojo POJO_A1 = new SimpleExtractorPojo(); 62 | private static final PojoWithNestedPojo POJO_A2 = new PojoWithNestedPojo(); 63 | private static final Pojo POJO_A3 = new Pojo(); 64 | private static final AccessorInterfacePojo POJO_A4 = new AccessorInterfacePojo(); 65 | private static final AnnotatedMethodPojo POJO_A5 = new AnnotatedMethodPojo(); 66 | private static final Empty POJO_A6 = new Empty(); 67 | private static final NotAnnotatedFieldsPojo POJO_A7 = new NotAnnotatedFieldsPojo(); 68 | private static final RenamedFieldPojo POJO_A8 = new RenamedFieldPojo(); 69 | private static final ChildPojo POJO_A9 = new ChildPojo(); 70 | private static final PojoWithEnumField POJO_A14 = new PojoWithEnumField(); 71 | private static final ComplexFieldRenamedPojo POJO_A16 = new ComplexFieldRenamedPojo(); 72 | private static final DoNothingLookupPojo POJO_A17 = new DoNothingLookupPojo(); 73 | 74 | private static final AnnotatedMEthodFailsPojo POJO_A10 = new AnnotatedMEthodFailsPojo(); 75 | private static final RepeatedFieldnamesPojo POJO_A11 = new RepeatedFieldnamesPojo(); 76 | private static final ConflictingLookupPojo POJO_A12 = new ConflictingLookupPojo(); 77 | private static final PojoWithUnextractableField POJO_A13 = new PojoWithUnextractableField(); 78 | private static final RecursiveLoopPojo1 POJO_A15 = new RecursiveLoopPojo1(); 79 | private static final MultipleAnnotatedMethodsPojo POJO_A18 = new MultipleAnnotatedMethodsPojo(); 80 | 81 | @ParameterizedTest(name = "Should {0}") 82 | @MethodSource("paramsForShouldProduceCorrectResult") 83 | public void shouldProduceCorrectResult(String name, BasePojo initial, JsonNode expected) throws Exception 84 | { 85 | //given 86 | AnnotatedObject annotatedObject = AnnotatedObject.createWithAnnotation(initial, LoggableType.class); 87 | 88 | //when 89 | LookupResult lookupResult = reflectionLookupUtils.strategyLookupForRootObj(annotatedObject); 90 | JsonNode result = objectMapper.readTree(objectMapper.writeValueAsString(lookupResult.executeForResult())); 91 | //then 92 | Assertions.assertEquals(expected, result); 93 | } 94 | 95 | private Stream paramsForShouldProduceCorrectResult() throws Exception 96 | { 97 | return Stream.of( 98 | Arguments.of( 99 | "perform extractor lookup", 100 | POJO_A1, 101 | prepareResult(POJO_A1) 102 | ), 103 | Arguments.of( 104 | "perform nested extractor lookup", 105 | POJO_A2, 106 | prepareResult(POJO_A2) 107 | ), 108 | Arguments.of( 109 | "perform even nested collector lookup", 110 | POJO_A3, 111 | prepareResult(POJO_A3) 112 | ), 113 | Arguments.of( 114 | "perform accessor method lookup", 115 | POJO_A4, 116 | prepareResult(POJO_A4) 117 | ), 118 | Arguments.of( 119 | "perform annotated method lookup", 120 | POJO_A5, 121 | prepareResult(POJO_A5) 122 | ), 123 | Arguments.of( 124 | "perform lookup on empty pojo", 125 | POJO_A6, 126 | prepareResult(POJO_A6) 127 | ), 128 | Arguments.of( 129 | "perform lookup on pojo without annotated fields", 130 | POJO_A7, 131 | prepareResult(POJO_A7) 132 | ), 133 | Arguments.of( 134 | "perform lookup on pojo with renamed field(s)", 135 | POJO_A8, 136 | prepareResult(POJO_A8) 137 | ), 138 | Arguments.of( 139 | "perform lookup in parent classes", 140 | POJO_A9, 141 | prepareResult(POJO_A9) 142 | ), 143 | Arguments.of( 144 | "should serialize enums", 145 | POJO_A14, 146 | prepareResult(POJO_A14) 147 | ), 148 | Arguments.of( 149 | "rename complex field", 150 | POJO_A16, 151 | prepareResult(POJO_A16) 152 | ), 153 | Arguments.of( 154 | "perform do nothing lookup", 155 | POJO_A17, 156 | prepareResult(POJO_A17) 157 | ) 158 | 159 | ); 160 | } 161 | 162 | private JsonNode prepareResult(BasePojo basePojo) throws IOException 163 | { 164 | return objectMapper.readTree(objectMapper.writeValueAsString(basePojo)); 165 | } 166 | 167 | @ParameterizedTest(name = "Should {0}") 168 | @MethodSource("paramsForShouldThrowException") 169 | public void shouldThrowException(String name, BasePojo initial, Class exceptionClass) 170 | { 171 | //given 172 | AnnotatedObject annotatedObject = AnnotatedObject.createWithAnnotation(initial, LoggableType.class); 173 | 174 | //when 175 | LookupResult lookupResult = reflectionLookupUtils.strategyLookupForRootObj(annotatedObject); 176 | 177 | //then 178 | Assertions.assertTrue(lookupResult.isExceptional()); 179 | try 180 | { 181 | lookupResult.executeForResult(); 182 | } catch (RuntimeException e) 183 | { 184 | Assertions.assertEquals(exceptionClass, Optional.ofNullable(e.getCause()).orElse(e).getClass()); 185 | return; 186 | } 187 | Assertions.fail(); 188 | } 189 | 190 | private Stream paramsForShouldThrowException() throws IOException 191 | { 192 | return Stream.of( 193 | Arguments.of( 194 | "throw exception if annotated method fails", 195 | POJO_A10, 196 | InvocationTargetException.class 197 | ), 198 | Arguments.of( 199 | "throw exception if field name is repeated in parent class and child", 200 | POJO_A11, 201 | RepeatedFieldsException.class 202 | ), 203 | Arguments.of( 204 | "throw exception on conflicting lookup", 205 | POJO_A12, 206 | LookupConflictException.class 207 | ), 208 | Arguments.of( 209 | "throw exception for unextractable field", 210 | POJO_A13, 211 | UnresolvedLookupException.class 212 | ), 213 | Arguments.of( 214 | "throw exception on recursive loop", 215 | POJO_A15, 216 | RecursiveLookupException.class 217 | ), 218 | Arguments.of( 219 | "throw exception if multiple annotated methods are present", 220 | POJO_A18, 221 | IllegalStateException.class 222 | ) 223 | ); 224 | } 225 | 226 | //TODO Omit nulls using property 227 | } -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/LookupResultTest.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype; 2 | 3 | import java.util.Map; 4 | import java.util.stream.Stream; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.TestInstance; 7 | import org.junit.jupiter.api.function.Executable; 8 | import org.junit.jupiter.params.ParameterizedTest; 9 | import org.junit.jupiter.params.provider.Arguments; 10 | import org.junit.jupiter.params.provider.MethodSource; 11 | import com.nixsolutions.logging.parameters.loggabletype.exception.UnresolvedLookupException; 12 | import com.google.common.collect.ImmutableMap; 13 | 14 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 15 | class LookupResultTest 16 | { 17 | private static final Object OBJECT = new Object(); 18 | 19 | private static final Map RESULT = ImmutableMap.of( 20 | "key1", ImmutableMap.of("key1_1", "key1_val1"), 21 | "key2", ImmutableMap.of("key2_1", "key2_val1")); 22 | 23 | @ParameterizedTest(name = "Should {0}") 24 | @MethodSource("argsForShouldBeCorrectType") 25 | public void shouldBeCorrectType(String caseName, LookupResult lookupResult, 26 | LookupResult.LookupType lookupTypeExpected) 27 | { 28 | Assertions.assertEquals(lookupTypeExpected, lookupResult.getLookupType()); 29 | 30 | } 31 | 32 | private Stream argsForShouldBeCorrectType() 33 | { 34 | return Stream.of( 35 | Arguments.of( 36 | "type be RESOLVED", 37 | createLookupResult(LookupResult.LookupType.RESOLVED), 38 | LookupResult.LookupType.RESOLVED 39 | ), 40 | Arguments.of( 41 | "type be UNRESOLVED", 42 | createLookupResult(LookupResult.LookupType.UNRESOLVED), 43 | LookupResult.LookupType.UNRESOLVED 44 | ), 45 | Arguments.of( 46 | "type be EXCEPTIONAL", 47 | createLookupResult(LookupResult.LookupType.EXCEPTIONAL), 48 | LookupResult.LookupType.EXCEPTIONAL 49 | ), 50 | Arguments.of( 51 | "type be LAZY", 52 | createLazyLookupWrapping(createLookupResult(LookupResult.LookupType.RESOLVED)), 53 | LookupResult.LookupType.LAZY 54 | ) 55 | ); 56 | } 57 | 58 | @ParameterizedTest(name = "Should be {0} after unwrapping lazy") 59 | @MethodSource("argsForShouldBeCorrectTypeOfLazyAfterUnwrap") 60 | public void shouldBeCorrectTypeOfLazyAfterUnwrap(String caseName, LookupResult lookupResult, 61 | LookupResult.LookupType lookupTypeExpected) 62 | { 63 | Assertions.assertEquals(lookupResult.getLookupType(), LookupResult.LookupType.LAZY); 64 | Assertions.assertTrue(lookupResult.isCertainLookupType(lookupTypeExpected)); 65 | Assertions.assertNotEquals(lookupResult.getLookupType(), LookupResult.LookupType.LAZY); 66 | } 67 | 68 | private Stream argsForShouldBeCorrectTypeOfLazyAfterUnwrap() 69 | { 70 | return Stream.of( 71 | Arguments.of( 72 | "type be RESOLVED", 73 | createLazyLookupWrapping(createLookupResult(LookupResult.LookupType.RESOLVED)), 74 | LookupResult.LookupType.RESOLVED 75 | ), 76 | Arguments.of( 77 | "type be UNRESOLVED", 78 | createLazyLookupWrapping(createLookupResult(LookupResult.LookupType.UNRESOLVED)), 79 | LookupResult.LookupType.UNRESOLVED 80 | ), 81 | Arguments.of( 82 | "type be EXCEPTIONAL", 83 | createLazyLookupWrapping(createLookupResult(LookupResult.LookupType.EXCEPTIONAL)), 84 | LookupResult.LookupType.EXCEPTIONAL 85 | ) 86 | ); 87 | } 88 | 89 | @ParameterizedTest(name = "Should throw {0}") 90 | @MethodSource("argsForShouldThrowExExceptionalAndUnresolvedLookup") 91 | public void shouldThrowExExceptionalAndUnresolvedLookup(Class expectedException, LookupResult 92 | lookupResult) 93 | { 94 | Executable executable = lookupResult::executeForResult; 95 | Assertions.assertThrows(expectedException, executable); 96 | } 97 | 98 | private Stream argsForShouldThrowExExceptionalAndUnresolvedLookup() 99 | { 100 | return Stream.of( 101 | Arguments.of( 102 | RuntimeException.class, 103 | createLookupResult(LookupResult.LookupType.EXCEPTIONAL) 104 | ), 105 | Arguments.of( 106 | UnresolvedLookupException.class, 107 | createLookupResult(LookupResult.LookupType.UNRESOLVED) 108 | ) 109 | ); 110 | } 111 | 112 | @ParameterizedTest(name = "Should {0}") 113 | @MethodSource("argsForShouldProduceCorrectResult") 114 | public void shouldProduceCorrectResult(String name, Map expectedResult, LookupResult lookupResult) 115 | { 116 | Assertions.assertEquals(expectedResult, lookupResult.executeForResult()); 117 | } 118 | 119 | private Stream argsForShouldProduceCorrectResult() 120 | { 121 | return Stream.of( 122 | Arguments.of( 123 | "return correct result for resolved lookup", 124 | RESULT, 125 | createLookupResult(LookupResult.LookupType.RESOLVED) 126 | ), 127 | Arguments.of( 128 | "return correct result for lazy resolved lookup", 129 | RESULT, 130 | createLazyLookupWrapping(createLookupResult(LookupResult.LookupType.RESOLVED)) 131 | ) 132 | ); 133 | } 134 | 135 | private LookupResult createLazyLookupWrapping(LookupResult lookupResultWrapped) 136 | { 137 | return LookupResult.lazy(() -> lookupResultWrapped); 138 | } 139 | 140 | private LookupResult createLookupResult(LookupResult.LookupType lookupType) 141 | { 142 | if (lookupType.equals(LookupResult.LookupType.RESOLVED)) 143 | { 144 | return LookupResult.createResolved(() -> RESULT); 145 | } 146 | else if (lookupType.equals(LookupResult.LookupType.UNRESOLVED)) 147 | { 148 | return LookupResult.createUnresolved(); 149 | } 150 | else 151 | { 152 | return LookupResult.createExceptional(RuntimeException::new); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/BasePojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases; 2 | 3 | public interface BasePojo 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/accessorinterface/AccessorInterfacePojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.accessorinterface; 2 | 3 | import java.util.Map; 4 | import com.nixsolutions.logging.annotation.LoggableType; 5 | import com.nixsolutions.logging.parameters.loggabletype.ContextParamsAccessor; 6 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 7 | import com.google.common.collect.ImmutableMap; 8 | 9 | @LoggableType 10 | public class AccessorInterfacePojo implements BasePojo, ContextParamsAccessor 11 | { 12 | public String field1 = "POJO_A4_FIELD_1"; 13 | 14 | @Override 15 | public Map extractParams() 16 | { 17 | return ImmutableMap.of("field1", field1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/annotatedmethod/AnnotatedMethodPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.annotatedmethod; 2 | 3 | import java.util.Map; 4 | import com.nixsolutions.logging.annotation.LoggableType; 5 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 6 | import com.fasterxml.jackson.annotation.JsonIgnore; 7 | import com.google.common.collect.ImmutableMap; 8 | 9 | @LoggableType 10 | public class AnnotatedMethodPojo implements BasePojo 11 | { 12 | public String field1 = "POJO_A5_FIELD_1"; 13 | 14 | @JsonIgnore 15 | @LoggableType.extractionMethod 16 | public Map getLogParams() 17 | { 18 | return ImmutableMap.of("field1", field1); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/annotatedmethodfails/AnnotatedMEthodFailsPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.annotatedmethodfails; 2 | 3 | import java.util.Map; 4 | import com.nixsolutions.logging.annotation.LoggableType; 5 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 6 | import com.fasterxml.jackson.annotation.JsonIgnore; 7 | 8 | @LoggableType 9 | public class AnnotatedMEthodFailsPojo implements BasePojo 10 | { 11 | public String field1 = "POJO_A10_FIELD_1"; 12 | 13 | @JsonIgnore 14 | @LoggableType.extractionMethod 15 | public Map getLogParams() 16 | { 17 | throw new RuntimeException(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/caseinheritance/ChildPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.caseinheritance; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType(ignoreParents = false) 7 | public class ChildPojo extends ParentPojo implements BasePojo 8 | { 9 | @LoggableType.property 10 | public String field1 = "POJO_A9_FIELD_1"; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/caseinheritance/GrandParentPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.caseinheritance; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | 5 | @LoggableType 6 | public class GrandParentPojo 7 | { 8 | @LoggableType.property 9 | public String field1b1 = "POJO_A9_BASE_1_FIELD_1b1"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/caseinheritance/ParentPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.caseinheritance; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType(ignoreParents = false) 7 | public class ParentPojo extends GrandParentPojo implements BasePojo 8 | { 9 | @LoggableType.property 10 | public String field1b2 = "POJO_A9_BASE_2_FIELD_1b2"; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/conflictinglookup/ConflictingLookupPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.conflictinglookup; 2 | 3 | import static com.nixsolutions.logging.parameters.loggabletype.ExtractionResolutionStrategy.RAISE_EX_ON_CONFLICT; 4 | import com.nixsolutions.logging.annotation.LoggableType; 5 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 6 | 7 | @LoggableType(resolutionStrategy = RAISE_EX_ON_CONFLICT) 8 | public class ConflictingLookupPojo implements BasePojo 9 | { 10 | @LoggableType.property 11 | public String field1 = "POJO_A12_FIELD_1"; 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/conflictinglookup/ConflictingLookupPojoExtractor.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.conflictinglookup; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import org.springframework.stereotype.Component; 6 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 7 | import com.google.common.collect.ImmutableList; 8 | import com.google.common.collect.ImmutableMap; 9 | 10 | @Component 11 | public class ConflictingLookupPojoExtractor implements ContextParamExtractor 12 | { 13 | @Override 14 | public Map extractParams(String name, ConflictingLookupPojo parameter) 15 | { 16 | return ImmutableMap.of("field1", parameter.field1); 17 | } 18 | 19 | @Override 20 | public List> getExtractableClasses() 21 | { 22 | return ImmutableList.of(ConflictingLookupPojo.class); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/donothinglookup/DoNothingLookupPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.donothinglookup; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.ExtractionResolutionStrategy; 5 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 6 | import com.fasterxml.jackson.annotation.JsonIgnore; 7 | 8 | @LoggableType(resolutionStrategy = ExtractionResolutionStrategy.DO_NOTHING) 9 | public class DoNothingLookupPojo implements BasePojo 10 | { 11 | @JsonIgnore 12 | @LoggableType.property 13 | public String field1 = "POJO_A17_FIELD_1"; 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/donothinglookup/DoNothingLookupPojoExtractor.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.donothinglookup; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import org.springframework.stereotype.Component; 6 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 7 | import com.google.common.collect.ImmutableList; 8 | import com.google.common.collect.ImmutableMap; 9 | 10 | @Component 11 | public class DoNothingLookupPojoExtractor implements ContextParamExtractor 12 | { 13 | @Override 14 | public Map extractParams(String name, DoNothingLookupPojo parameter) 15 | { 16 | return ImmutableMap.of(name, parameter.toString()); 17 | } 18 | 19 | @Override 20 | public List> getExtractableClasses() 21 | { 22 | return ImmutableList.of(DoNothingLookupPojo.class); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/emptypojo/Empty.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.emptypojo; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType 7 | public class Empty implements BasePojo 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/enumtypefield/EnumType.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.enumtypefield; 2 | 3 | public enum EnumType 4 | { 5 | ALPHA, BETA, GAMMA 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/enumtypefield/PojoWithEnumField.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.enumtypefield; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType 7 | public class PojoWithEnumField implements BasePojo 8 | { 9 | @LoggableType.property 10 | public EnumType foreignPojo = EnumType.ALPHA; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/multipleannotatedmethods/MultipleAnnotatedMethodsPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.multipleannotatedmethods; 2 | 3 | import java.util.Map; 4 | import com.nixsolutions.logging.annotation.LoggableType; 5 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 6 | import com.google.common.collect.ImmutableMap; 7 | 8 | @LoggableType 9 | public class MultipleAnnotatedMethodsPojo implements BasePojo 10 | { 11 | 12 | @LoggableType.extractionMethod 13 | public Map extract1() 14 | { 15 | return ImmutableMap.of(); 16 | } 17 | 18 | @LoggableType.extractionMethod 19 | public Map extract2() 20 | { 21 | return ImmutableMap.of(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/nestedcollector/EvenMoreNestedPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.nestedcollector; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | 5 | @LoggableType 6 | public class EvenMoreNestedPojo 7 | { 8 | @LoggableType.property 9 | public String field1 = "POJO_C3_FIELD_1"; 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/nestedcollector/NestedPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.nestedcollector; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | 5 | @LoggableType 6 | public class NestedPojo 7 | { 8 | @LoggableType.property 9 | public String field1 = "POJO_B3_FIELD_1"; 10 | 11 | @LoggableType.property 12 | public EvenMoreNestedPojo pojoC3 = new EvenMoreNestedPojo(); 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/nestedcollector/Pojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.nestedcollector; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType 7 | public class Pojo implements BasePojo 8 | { 9 | @LoggableType.property 10 | public String field1 = "POJO_A3_FIELD_1"; 11 | 12 | @LoggableType.property 13 | public NestedPojo pojoB3 = new NestedPojo(); 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/nestedextractor/NestedPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.nestedextractor; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.ExtractionResolutionStrategy; 5 | 6 | @LoggableType(resolutionStrategy = ExtractionResolutionStrategy.EXTRACTOR_FIRST) 7 | public class NestedPojo 8 | { 9 | @LoggableType.property 10 | public String field1 = "POJO_B2_FIELD_1"; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/nestedextractor/NestedPojoExtractor.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.nestedextractor; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Map; 6 | import org.springframework.stereotype.Component; 7 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 8 | import com.google.common.collect.ImmutableMap; 9 | 10 | @Component 11 | public class NestedPojoExtractor implements ContextParamExtractor 12 | { 13 | @Override 14 | public Map extractParams(String name, NestedPojo parameter) 15 | { 16 | if (name.isEmpty()) 17 | { 18 | return ImmutableMap.of( 19 | "field1", parameter.field1); 20 | } 21 | else 22 | return ImmutableMap.of(); 23 | } 24 | 25 | @Override 26 | public List> getExtractableClasses() 27 | { 28 | return Arrays.asList(NestedPojo.class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/nestedextractor/PojoWithNestedPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.nestedextractor; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType 7 | public class PojoWithNestedPojo implements BasePojo 8 | { 9 | @LoggableType.property 10 | public String field1 = "POJO_A2_FIELD_1"; 11 | 12 | @LoggableType.property 13 | public NestedPojo pojoB2 = new NestedPojo(); 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/notannotatedfields/NotAnnotatedFieldsPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.notannotatedfields; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | import com.fasterxml.jackson.annotation.JsonIgnore; 6 | 7 | @LoggableType 8 | public class NotAnnotatedFieldsPojo implements BasePojo 9 | { 10 | @JsonIgnore 11 | public String field1 = "field1"; 12 | 13 | @JsonIgnore 14 | public String field2 = "field2"; 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/recursivefail/RecursiveLoopPojo1.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.recursivefail; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType 7 | public class RecursiveLoopPojo1 implements BasePojo 8 | { 9 | public RecursiveLoopPojo1() 10 | { 11 | pojoB15 = new RecursiveLoopPojo2(); 12 | pojoB15.pojoA15 = this; 13 | } 14 | 15 | @LoggableType.property 16 | public RecursiveLoopPojo2 pojoB15; 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/recursivefail/RecursiveLoopPojo2.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.recursivefail; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | 5 | @LoggableType 6 | public class RecursiveLoopPojo2 7 | { 8 | @LoggableType.property 9 | public RecursiveLoopPojo1 pojoA15; 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/renamedcomplexfield/ComplexFieldRenamedPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.renamedcomplexfield; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @LoggableType 8 | public class ComplexFieldRenamedPojo implements BasePojo 9 | { 10 | @JsonProperty("pojob16") 11 | @LoggableType.property(name = "pojob16") 12 | public ComplexPojo field1 = new ComplexPojo(); 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/renamedcomplexfield/ComplexPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.renamedcomplexfield; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | 5 | @LoggableType 6 | public class ComplexPojo 7 | { 8 | @LoggableType.property 9 | public String field1 = "POJO_B16_FIELD_1"; 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/renamedfield/RenamedFieldPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.renamedfield; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @LoggableType 8 | public class RenamedFieldPojo implements BasePojo 9 | { 10 | @JsonProperty("field1Renamed") 11 | @LoggableType.property(name = "field1Renamed") 12 | public String field1 = "POJO_A8_FIELD_1_RENAMED"; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/repeatedfieldnames/RepatedFieldsParentPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.repeatedfieldnames; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType 7 | public class RepatedFieldsParentPojo implements BasePojo 8 | { 9 | @LoggableType.property 10 | public String field1 = "POJO_A11_BASE_1_FIELD_1"; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/repeatedfieldnames/RepeatedFieldnamesPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.repeatedfieldnames; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType(ignoreParents = false) 7 | public class RepeatedFieldnamesPojo extends RepatedFieldsParentPojo implements BasePojo 8 | { 9 | @LoggableType.property 10 | public String field1 = "POJO_A11_FIELD_1"; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/simpleextractor/SimpleExtractorPojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.simpleextractor; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.ExtractionResolutionStrategy; 5 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 6 | 7 | @LoggableType(resolutionStrategy = ExtractionResolutionStrategy.EXTRACTOR_FIRST) 8 | public class SimpleExtractorPojo implements BasePojo 9 | { 10 | public String field1 = "POJO_A1_FIELD_1"; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/simpleextractor/SimpleExtractorPojoExtractor.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.simpleextractor; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Map; 6 | import org.springframework.stereotype.Component; 7 | import com.nixsolutions.logging.parameters.extractor.ContextParamExtractor; 8 | import com.google.common.collect.ImmutableMap; 9 | 10 | @Component 11 | public class SimpleExtractorPojoExtractor implements ContextParamExtractor 12 | { 13 | @Override 14 | public Map extractParams(String name, SimpleExtractorPojo parameter) 15 | { 16 | if(name.isEmpty()) 17 | { 18 | return ImmutableMap.of("field1", parameter.field1); 19 | } 20 | else 21 | return ImmutableMap.of(); 22 | } 23 | 24 | @Override 25 | public List> getExtractableClasses() 26 | { 27 | return Arrays.asList(SimpleExtractorPojo.class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/unextractablefield/PojoWithUnextractableField.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.unextractablefield; 2 | 3 | import com.nixsolutions.logging.annotation.LoggableType; 4 | import com.nixsolutions.logging.parameters.loggabletype.cases.BasePojo; 5 | 6 | @LoggableType 7 | public class PojoWithUnextractableField implements BasePojo 8 | { 9 | @LoggableType.property 10 | public UnextractablePojo foreignPojo = new UnextractablePojo(); 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/nixsolutions/logging/parameters/loggabletype/cases/unextractablefield/UnextractablePojo.java: -------------------------------------------------------------------------------- 1 | package com.nixsolutions.logging.parameters.loggabletype.cases.unextractablefield; 2 | 3 | public class UnextractablePojo 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/entry/complexParam.json: -------------------------------------------------------------------------------- 1 | { 2 | "complexParam": { 3 | "strParam": "STR_PARAM", 4 | "longParam": 1, 5 | "enumParam": "ENUM_PARAM" 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/entry/emptyCtx.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/entry/multipleParams.json: -------------------------------------------------------------------------------- 1 | { 2 | "strParam": "STR_PARAM", 3 | "longParam": 1 4 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/entry/renamedParam.json: -------------------------------------------------------------------------------- 1 | { 2 | "renamedStrParam": "STR_PARAM" 3 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/entry/strParam.json: -------------------------------------------------------------------------------- 1 | { 2 | "strParam": "STR_PARAM" 3 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/exectime/adjustedTaskName.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeLoggingContext": { 3 | "taskName": "newtaskname", 4 | "duration": 1, 5 | "timeUnit": "MILLISECONDS" 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/exectime/changedTimeUnit.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeLoggingContext": { 3 | "taskName": "methodWithExectimeLoggingAndOtherTimeUnit", 4 | "duration": 1, 5 | "timeUnit": "MICROSECONDS" 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/exectime/humanReadableTaskName.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeLoggingContext": { 3 | "taskName": "humanReadableTaskName", 4 | "duration": 1, 5 | "timeUnit": "MILLISECONDS" 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/exectime/simpleTimeLogging.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeLoggingContext": { 3 | "taskName": "methodWihExecTimeLogging", 4 | "duration": 1, 5 | "timeUnit": "MILLISECONDS" 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/exit/pojoReturnParam.json: -------------------------------------------------------------------------------- 1 | { 2 | "@return": { 3 | "strParam": "STR_PARAM", 4 | "longParam": 1, 5 | "enumParam": "ENUM_PARAM" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/exit/strReturnParam.json: -------------------------------------------------------------------------------- 1 | { 2 | "@return": "RETURN_STR" 3 | } -------------------------------------------------------------------------------- /src/test/resources/com/nixsolutions/logging/advice/exit/voidReturn.json: -------------------------------------------------------------------------------- 1 | { 2 | "@return": "void" 3 | } -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | { 11 | "context": "#asJson{%message}" 12 | } 13 | 14 | 15 | 16 | 17 | { 18 | "exception": "%exception" 19 | } 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------