├── .gitignore
├── AUTHORS.txt
├── LICENSE.txt
├── README.md
├── build.gradle
├── circle.yml
├── gradle.properties
├── gradle
├── deploy.gradle
├── readme.gradle
├── readme.template
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── loggly
├── build.gradle
├── gradle.properties
└── src
│ ├── main
│ └── java
│ │ └── ch
│ │ └── qos
│ │ └── logback
│ │ └── ext
│ │ └── loggly
│ │ ├── AbstractLogglyAppender.java
│ │ ├── LogglyAppender.java
│ │ ├── LogglyBatchAppender.java
│ │ ├── LogglyBatchAppenderMBean.java
│ │ └── io
│ │ ├── DiscardingRollingOutputStream.java
│ │ └── IoUtils.java
│ └── test
│ ├── java
│ └── ch
│ │ └── qos
│ │ └── logback
│ │ └── ext
│ │ └── loggly
│ │ ├── HttpTestServer.java
│ │ ├── LogglyBatchAppenderTest.java
│ │ ├── LogglyHttpAppenderIntegratedTest.java
│ │ └── LogglySendOnlyWhenMaxBucketsFullTest.java
│ └── resources
│ └── logback-test.xml
├── scripts
├── deploysnapshot.sh
├── nexus.sh
└── release.sh
├── settings.gradle
└── spring
├── build.gradle
├── gradle.properties
└── src
└── main
└── java
└── ch
└── qos
└── logback
└── ext
└── spring
├── ApplicationContextHolder.java
├── DelegatingLogbackAppender.java
├── EventCacheMode.java
├── ILoggingEventCache.java
├── LogbackConfigurer.java
└── web
├── LogbackConfigListener.java
├── LogbackConfigServlet.java
└── WebLogbackConfigurer.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.idea
2 | *.iml
3 | *.ipr
4 | *.iws
5 | .classpath
6 | .externalToolBuilders
7 | .gradle
8 | .project
9 | .settings
10 | .gradle
11 | target
12 | out
13 | build
14 | local.properties
15 |
--------------------------------------------------------------------------------
/AUTHORS.txt:
--------------------------------------------------------------------------------
1 | Original author
2 | ---------------
3 |
4 | Hazlewood, Les {
5 | Loggly Extension
6 | Spring Extension
7 | }
8 |
9 |
10 | Contributors (alphabetical)
11 | ---------------------------
12 |
13 | Gulcu, Ceki {
14 | Maintainer
15 | }
16 |
17 | Trinh, Anthony {
18 | Maintainer
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2014 The logback-extensions developers.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # logback-extensions [](https://circleci.com/gh/https://circleci.com/gh/qos-ch/logback-extensions)
2 | v0.1.5
3 |
4 | https://github.com/qos-ch/logback-extensions/wiki
5 |
6 | #### Build Instructions
7 | Run the following command to build:
8 |
9 | ```
10 | ./gradlew clean assemble
11 | ```
12 |
13 | #### License
14 | ```
15 | Licensed under the Apache License, Version 2.0 (the "License");
16 | you may not use this file except in compliance with the License.
17 | You may obtain a copy of the License at
18 |
19 | http://www.apache.org/licenses/LICENSE-2.0
20 |
21 | Unless required by applicable law or agreed to in writing, software
22 | distributed under the License is distributed on an "AS IS" BASIS,
23 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 | See the License for the specific language governing permissions and
25 | limitations under the License.
26 | ```
27 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 | dependencies {
7 | // classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
8 | classpath 'org.ajoberstar:grgit:2.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 | plugins {
15 | id 'io.codearte.nexus-staging' version '0.11.0' // must be in root project
16 | id 'net.researchgate.release' version '2.6.0'
17 | }
18 | apply plugin: 'org.ajoberstar.grgit'
19 | apply from: 'gradle/readme.gradle'
20 |
21 | allprojects {
22 | apply plugin: 'maven'
23 |
24 | group = 'org.logback-extensions'
25 | version = VERSION_NAME
26 | }
27 |
28 | subprojects {
29 | apply plugin: 'java'
30 | apply from: "${rootProject.rootDir}/gradle/deploy.gradle"
31 |
32 | sourceCompatibility = JavaVersion.VERSION_1_8
33 | targetCompatibility = JavaVersion.VERSION_1_8
34 | tasks.withType(JavaCompile) {
35 | options.encoding = 'UTF-8'
36 | // Warn about deprecations
37 | //options.compilerArgs << '-Xlint:deprecation'
38 | // Warn about unchecked usages
39 | options.compilerArgs << '-Xlint:unchecked'
40 | // Don't warn about using source/target 1.5 option
41 | options.compilerArgs << '-Xlint:-options'
42 |
43 | options.debug(['debugLevel': 'source,lines,vars'])
44 | options.debug = VERSION_NAME.contains('SNAPSHOT')
45 | }
46 |
47 | repositories {
48 | mavenLocal()
49 | jcenter()
50 | maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
51 | }
52 |
53 | dependencies {
54 | testCompile 'org.mockito:mockito-core:1.9.0'
55 | testCompile 'junit:junit:4.12'
56 | }
57 |
58 | task sourcesJar(type: Jar, dependsOn: classes) {
59 | classifier = 'sources'
60 | from sourceSets.main.allSource
61 | }
62 |
63 | javadoc {
64 | failOnError false
65 | // disable javadoc lint warnings (e.g., missing javadoc)
66 | options.addBooleanOption('Xdoclint:none', true)
67 | }
68 |
69 | task javadocJar(type: Jar, dependsOn: javadoc) {
70 | classifier = 'javadoc'
71 | from javadoc.destinationDir
72 | }
73 |
74 | artifacts {
75 | archives sourcesJar
76 | archives javadocJar
77 | }
78 | }
79 |
80 | release {
81 | tagTemplate = 'v_${version}'
82 | preTagCommitMessage = ':cloud: Release'
83 | tagCommitMessage = ':cloud: Release'
84 | newVersionCommitMessage = ':cloud: Bump'
85 |
86 | // versionPropertyFile = '../gradle.properties'
87 | versionProperties = ['VERSION_NAME']
88 |
89 | git {
90 | requireBranch = ''
91 | }
92 | }
93 |
94 | nexusStaging {
95 | packageGroup = 'org.logback-extensions'
96 | }
97 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | # Configuration for CircleCI
2 | # https://circleci.com/gh/qos-ch/logback-extensions
3 | version: 2.0
4 |
5 | jobs:
6 | build:
7 | branches:
8 | ignore: gh-pages
9 | working_directory: ~/code
10 | docker:
11 | - image: circleci/openjdk:8-jdk-browsers
12 | environment:
13 | JVM_OPTS: -Xmx3200m
14 | steps:
15 | - checkout
16 | - restore_cache:
17 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "loggly/build.gradle" }}-{{ checksum "spring/build.gradle" }}
18 | # - run:
19 | # name: Chmod permissions #if permission for Gradlew Dependencies fail, use this.
20 | # command: sudo chmod +x ./gradlew
21 | - save_cache:
22 | paths:
23 | - ~/.gradle
24 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "loggly/build.gradle" }}-{{ checksum "spring/build.gradle" }}
25 | - run:
26 | name: Run Tests
27 | command: ./gradlew check assemble --parallel
28 | - store_artifacts:
29 | path: loggly/build/reports
30 | destination: reports
31 | - store_artifacts:
32 | path: spring/build/reports
33 | destination: reports
34 | - store_artifacts:
35 | path: loggly/build/outputs
36 | destination: outputs
37 | - store_artifacts:
38 | path: spring/build/outputs
39 | destination: outputs
40 | - store_test_results:
41 | path: loggly/build/test-results
42 | - store_test_results:
43 | path: spring/build/test-results
44 | - run:
45 | name: Deploy snapshot
46 | command: scripts/deploysnapshot.sh
47 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # `version` is needed by gradle-release-plugin and always matches
2 | # `VERSION_NAME`. The plugin updates both automatically.
3 | version=0.1.6-SNAPSHOT
4 | VERSION_NAME=0.1.6-SNAPSHOT
5 |
6 | GROUP=org.logback-extensions
7 |
8 | POM_URL=https://github.com/qos-ch/logback-extensions
9 | POM_SCM_URL=https://github.com/qos-ch/logback-extensions
10 | POM_SCM_ISSUES_URL=https://github.com/qos-ch/logback-extensions/issues
11 | POM_SCM_CONNECTION=scm:git@github.com:qos-ch/logback-extensions.git
12 | POM_SCM_DEV_CONNECTION=scm:git@github.com:qos-ch/logback-extensions.git
13 | POM_LICENCE_NAME="The Apache Software License, Version 2.0"
14 | POM_LICENCE_ID="Apache-2.0"
15 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
16 | POM_LICENCE_DIST=repo
17 | POM_DEVELOPER_ID=tony19
18 | POM_DEVELOPER_NAME=tony19
19 | POM_DEVELOPER_EMAIL=tony19@gmail.com
20 |
--------------------------------------------------------------------------------
/gradle/deploy.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Chris Banes
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | apply plugin: 'maven'
17 | apply plugin: 'signing'
18 | //apply plugin: 'com.jfrog.bintray'
19 | //
20 | //bintray {
21 | // user = hasProperty('BINTRAY_USER') ? BINTRAY_USER : ''
22 | // key = hasProperty('BINTRAY_KEY') ? BINTRAY_KEY : ''
23 | // configurations = ['archives']
24 | //
25 | // publish = true
26 | // override = true
27 | //
28 | // pkg {
29 | // repo = 'generic'
30 | // name = "${GROUP}:${POM_NAME}"
31 | // desc = POM_DESCRIPTION
32 | // licenses = [POM_LICENCE_ID]
33 | // labels = ['android', 'logging']
34 | // websiteUrl = POM_URL
35 | // issueTrackerUrl = POM_SCM_ISSUES_URL
36 | // vcsUrl = POM_SCM_URL
37 | // githubRepo = POM_SCM_URL
38 | //
39 | // version {
40 | // name = VERSION_NAME
41 | // released = new Date()
42 | // vcsTag = "v_${VERSION_NAME}"
43 | // gpg {
44 | // sign = true
45 | // }
46 | // }
47 | // }
48 | //}
49 |
50 | def isReleaseBuild() {
51 | return !VERSION_NAME.contains("SNAPSHOT")
52 | }
53 |
54 | def getReleaseRepositoryUrl() {
55 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
56 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
57 | }
58 |
59 | def getSnapshotRepositoryUrl() {
60 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
61 | : "https://oss.sonatype.org/content/repositories/snapshots/"
62 | }
63 |
64 | def getRepositoryUsername() {
65 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ''
66 | }
67 |
68 | def getRepositoryPassword() {
69 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ''
70 | }
71 |
72 | afterEvaluate { project ->
73 | uploadArchives {
74 | repositories {
75 | mavenDeployer {
76 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
77 |
78 | pom.groupId = GROUP
79 | pom.artifactId = POM_ARTIFACT_ID
80 | pom.version = VERSION_NAME
81 |
82 | repository(url: getReleaseRepositoryUrl()) {
83 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
84 | }
85 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
86 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
87 | }
88 |
89 | pom.project {
90 | name POM_NAME
91 | packaging POM_PACKAGING
92 | description POM_DESCRIPTION
93 | url POM_URL
94 |
95 | scm {
96 | url POM_SCM_URL
97 | connection POM_SCM_CONNECTION
98 | developerConnection POM_SCM_DEV_CONNECTION
99 | }
100 |
101 | licenses {
102 | license {
103 | name POM_LICENCE_NAME
104 | url POM_LICENCE_URL
105 | distribution POM_LICENCE_DIST
106 | }
107 | }
108 |
109 | developers {
110 | developer {
111 | id POM_DEVELOPER_ID
112 | name POM_DEVELOPER_NAME
113 | email POM_DEVELOPER_EMAIL
114 | }
115 | }
116 | }
117 | }
118 | }
119 | }
120 |
121 | signing {
122 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
123 | sign configurations.archives
124 | }
125 | }
--------------------------------------------------------------------------------
/gradle/readme.gradle:
--------------------------------------------------------------------------------
1 | /**
2 | * Generates README.md based on project properties, and
3 | * pushes it to GitHub if rootProject.hasProperty('push')
4 | */
5 | task readme {
6 | description 'Updates README.md, and pushes it to GitHub.'
7 |
8 | doLast {
9 | def updateReadme = {
10 | def text = new File('gradle/readme.template').getText('UTF-8')
11 | def template = new groovy.text.StreamingTemplateEngine().createTemplate(text)
12 |
13 | def binding = [
14 | version : rootProject.VERSION_NAME - ~/-SNAPSHOT/,
15 | ]
16 |
17 | String newText = template.make(binding)
18 | newText = newText.replace('\\u007B', '{')
19 |
20 | def updated = false
21 | def readmeFile = new File('README.md')
22 | if (readmeFile.text != newText) {
23 | readmeFile.text = newText
24 | updated = true
25 | }
26 | return updated
27 | }
28 |
29 | def commitReadme = {
30 | grgit.add(patterns: ['README.md'])
31 | grgit.commit(message: ":books: Update README for ${rootProject.version}")
32 | grgit.push()
33 | }
34 |
35 | if (updateReadme() && rootProject.hasProperty('push')) {
36 | logger.info "committing README for ${rootProject.version}"
37 | commitReadme()
38 | } else {
39 | logger.info "no README changes for ${rootProject.version}"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/gradle/readme.template:
--------------------------------------------------------------------------------
1 | # logback-extensions [](https://circleci.com/gh/https://circleci.com/gh/qos-ch/logback-extensions)
2 | v${version}
3 |
4 | https://github.com/qos-ch/logback-extensions/wiki
5 |
6 | #### Build Instructions
7 | Run the following command to build:
8 |
9 | ```
10 | ./gradlew clean assemble
11 | ```
12 |
13 | #### License
14 | ```
15 | Licensed under the Apache License, Version 2.0 (the "License");
16 | you may not use this file except in compliance with the License.
17 | You may obtain a copy of the License at
18 |
19 | http://www.apache.org/licenses/LICENSE-2.0
20 |
21 | Unless required by applicable law or agreed to in writing, software
22 | distributed under the License is distributed on an "AS IS" BASIS,
23 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 | See the License for the specific language governing permissions and
25 | limitations under the License.
26 | ```
27 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qos-ch/logback-extensions/2d7dea74a2585d2ec724e3c3da2e9f4bf6fca99d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-bin.zip
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/loggly/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | description = POM_DESCRIPTION
3 | dependencies {
4 | testCompile 'de.sven-jacobs:loremipsum:1.0'
5 | testCompile 'org.simpleframework:simple:5.0.4'
6 | compile 'ch.qos.logback:logback-classic:1.2.3'
7 | }
8 |
--------------------------------------------------------------------------------
/loggly/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=logback-ext-loggly
2 | POM_ARTIFACT_ID=logback-ext-loggly
3 | POM_PACKAGING=jar
4 | POM_DESCRIPTION="Logback Extensions :: Loggly"
5 |
--------------------------------------------------------------------------------
/loggly/src/main/java/ch/qos/logback/ext/loggly/AbstractLogglyAppender.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly;
17 |
18 | import ch.qos.logback.classic.PatternLayout;
19 | import ch.qos.logback.core.Context;
20 | import ch.qos.logback.core.Layout;
21 | import ch.qos.logback.core.UnsynchronizedAppenderBase;
22 |
23 | import java.io.ByteArrayOutputStream;
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.net.InetSocketAddress;
27 | import java.net.Proxy;
28 | import java.nio.charset.Charset;
29 |
30 | /**
31 | * Common base for Loggly appenders.
32 | *
33 | * @author Mårten Gustafson
34 | * @author Les Hazlewood
35 | * @author Cyrille Le Clerc
36 | */
37 | public abstract class AbstractLogglyAppender extends UnsynchronizedAppenderBase {
38 | public static final String DEFAULT_ENDPOINT_PREFIX = "https://logs-01.loggly.com/";
39 | public static final String DEFAULT_LAYOUT_PATTERN = "%d{\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\",UTC} %-5level [%thread] %logger: %m%n";
40 | protected static final Charset UTF_8 = Charset.forName("UTF-8");
41 | protected String endpointUrl;
42 | protected String inputKey;
43 | protected Layout layout;
44 | protected boolean layoutCreatedImplicitly = false;
45 | private String pattern;
46 |
47 | private int proxyPort;
48 | private String proxyHost;
49 | protected Proxy proxy;
50 | private int httpReadTimeoutInMillis = 1000;
51 |
52 | @Override
53 | public void start() {
54 | ensureLayout();
55 | if (!this.layout.isStarted()) {
56 | this.layout.start();
57 | }
58 | if (this.endpointUrl == null) {
59 | if (this.inputKey == null) {
60 | addError("inputKey (or alternatively, endpointUrl) must be configured");
61 | } else {
62 | this.endpointUrl = buildEndpointUrl(this.inputKey);
63 | }
64 | }
65 |
66 | if (this.proxyHost == null || this.proxyHost.isEmpty()) {
67 | // don't set it to Proxy.NO_PROXY (i.e. Proxy.Type.DIRECT) as the meaning is different (user-jvm-proxy-config vs. don't use proxy)
68 | this.proxy = null;
69 | } else {
70 | this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
71 | }
72 | super.start();
73 | }
74 |
75 | @Override
76 | public void stop() {
77 | super.stop();
78 | if (this.layoutCreatedImplicitly) {
79 | try {
80 | this.layout.stop();
81 | } finally {
82 | this.layout = null;
83 | this.layoutCreatedImplicitly = false;
84 | }
85 | }
86 | }
87 |
88 | protected byte[] toBytes(final InputStream is) throws IOException {
89 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
90 | int count;
91 | byte[] buf = new byte[512];
92 |
93 | while((count = is.read(buf, 0, buf.length)) != -1) {
94 | baos.write(buf, 0, count);
95 | }
96 | baos.flush();
97 |
98 | return baos.toByteArray();
99 | }
100 |
101 | protected String readResponseBody(final InputStream input) throws IOException {
102 | try {
103 | final byte[] bytes = toBytes(input);
104 | return new String(bytes, UTF_8);
105 | } finally {
106 | input.close();
107 | }
108 | }
109 |
110 | protected final void ensureLayout() {
111 | if (this.layout == null) {
112 | this.layout = createLayout();
113 | this.layoutCreatedImplicitly = true;
114 | }
115 | if (this.layout != null) {
116 | Context context = this.layout.getContext();
117 | if (context == null) {
118 | this.layout.setContext(getContext());
119 | }
120 | }
121 | }
122 |
123 | @SuppressWarnings("unchecked")
124 | protected Layout createLayout() {
125 | PatternLayout layout = new PatternLayout();
126 | String pattern = getPattern();
127 | if (pattern == null) {
128 | pattern = DEFAULT_LAYOUT_PATTERN;
129 | }
130 | layout.setPattern(pattern);
131 | return (Layout) layout;
132 | }
133 |
134 | protected String buildEndpointUrl(String inputKey) {
135 | return new StringBuilder(DEFAULT_ENDPOINT_PREFIX).append(getEndpointPrefix())
136 | .append(inputKey).toString();
137 | }
138 |
139 | /**
140 | * Returns the URL path prefix for the Loggly endpoint to which the
141 | * implementing class will send log events. This path prefix varies
142 | * for the different Loggly services. The final endpoint URL is built
143 | * by concatenating the {@link #DEFAULT_ENDPOINT_PREFIX} with the
144 | * endpoint prefix from {@link #getEndpointPrefix()} and the
145 | * {@link #inputKey}.
146 | *
147 | * @return the URL path prefix for the Loggly endpoint
148 | */
149 | protected abstract String getEndpointPrefix();
150 |
151 | public String getEndpointUrl() {
152 | return endpointUrl;
153 | }
154 |
155 | public void setEndpointUrl(String endpointUrl) {
156 | this.endpointUrl = endpointUrl;
157 | }
158 |
159 | public String getInputKey() {
160 | return inputKey;
161 | }
162 |
163 | public void setInputKey(String inputKey) {
164 | String cleaned = inputKey;
165 | if (cleaned != null) {
166 | cleaned = cleaned.trim();
167 | }
168 | if ("".equals(cleaned)) {
169 | cleaned = null;
170 | }
171 | this.inputKey = cleaned;
172 | }
173 |
174 | public String getPattern() {
175 | return pattern;
176 | }
177 |
178 | public void setPattern(String pattern) {
179 | this.pattern = pattern;
180 | }
181 |
182 | public Layout getLayout() {
183 | return layout;
184 | }
185 |
186 | public void setLayout(Layout layout) {
187 | this.layout = layout;
188 | }
189 |
190 | public int getProxyPort() {
191 | return proxyPort;
192 | }
193 |
194 | public void setProxyPort(int proxyPort) {
195 | this.proxyPort = proxyPort;
196 | }
197 | public void setProxyPort(String proxyPort) {
198 | if(proxyPort == null || proxyPort.trim().isEmpty()) {
199 | // handle logback configuration default value like "${logback.loggly.proxy.port:-}"
200 | proxyPort = "0";
201 | }
202 | this.proxyPort = Integer.parseInt(proxyPort);
203 | }
204 |
205 | public String getProxyHost() {
206 | return proxyHost;
207 | }
208 |
209 | public void setProxyHost(String proxyHost) {
210 | if(proxyHost == null || proxyHost.trim().isEmpty()) {
211 | // handle logback configuration default value like "${logback.loggly.proxy.host:-}"
212 | proxyHost = null;
213 | }
214 | this.proxyHost = proxyHost;
215 | }
216 |
217 | public int getHttpReadTimeoutInMillis() {
218 | return httpReadTimeoutInMillis;
219 | }
220 |
221 | public void setHttpReadTimeoutInMillis(int httpReadTimeoutInMillis) {
222 | this.httpReadTimeoutInMillis = httpReadTimeoutInMillis;
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/loggly/src/main/java/ch/qos/logback/ext/loggly/LogglyAppender.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly;
17 |
18 | import java.io.IOException;
19 | import java.io.OutputStream;
20 | import java.net.HttpURLConnection;
21 | import java.net.URL;
22 |
23 | /**
24 | * An Appender that posts logging messages to Loggly, a cloud logging service.
25 | *
26 | * @author Mårten Gustafson
27 | * @author Les Hazlewood
28 | * @since 0.1
29 | */
30 | public class LogglyAppender extends AbstractLogglyAppender {
31 |
32 | public static final String ENDPOINT_URL_PATH = "inputs/";
33 |
34 | public LogglyAppender() {
35 | }
36 |
37 | @Override
38 | protected void append(E eventObject) {
39 | String msg = this.layout.doLayout(eventObject);
40 | postToLoggly(msg);
41 | }
42 |
43 | private void postToLoggly(final String event) {
44 | try {
45 | assert endpointUrl != null;
46 | URL endpoint = new URL(endpointUrl);
47 | final HttpURLConnection connection;
48 | if (proxy == null) {
49 | connection = (HttpURLConnection) endpoint.openConnection();
50 | } else {
51 | connection = (HttpURLConnection) endpoint.openConnection(proxy);
52 | }
53 | connection.setRequestMethod("POST");
54 | connection.setDoOutput(true);
55 | connection.addRequestProperty("Content-Type", this.layout.getContentType());
56 | connection.connect();
57 | sendAndClose(event, connection.getOutputStream());
58 | connection.disconnect();
59 | final int responseCode = connection.getResponseCode();
60 | if (responseCode != 200) {
61 | final String message = readResponseBody(connection.getInputStream());
62 | addError("Loggly post failed (HTTP " + responseCode + "). Response body:\n" + message);
63 | }
64 | } catch (final IOException e) {
65 | addError("IOException while attempting to communicate with Loggly", e);
66 | }
67 | }
68 |
69 | private void sendAndClose(final String event, final OutputStream output) throws IOException {
70 | try {
71 | output.write(event.getBytes("UTF-8"));
72 | } finally {
73 | output.close();
74 | }
75 | }
76 |
77 | @Override
78 | protected String getEndpointPrefix() {
79 | return ENDPOINT_URL_PATH;
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/loggly/src/main/java/ch/qos/logback/ext/loggly/LogglyBatchAppender.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly;
17 |
18 | import java.io.BufferedOutputStream;
19 | import java.io.ByteArrayInputStream;
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.lang.management.ManagementFactory;
24 | import java.net.HttpURLConnection;
25 | import java.net.URL;
26 | import java.nio.charset.Charset;
27 | import java.sql.Timestamp;
28 | import java.util.concurrent.BlockingDeque;
29 | import java.util.concurrent.Executors;
30 | import java.util.concurrent.ScheduledExecutorService;
31 | import java.util.concurrent.ThreadFactory;
32 | import java.util.concurrent.TimeUnit;
33 | import java.util.concurrent.atomic.AtomicInteger;
34 | import java.util.concurrent.atomic.AtomicLong;
35 |
36 | import javax.management.MBeanServer;
37 | import javax.management.ObjectName;
38 |
39 | import ch.qos.logback.ext.loggly.io.DiscardingRollingOutputStream;
40 | import ch.qos.logback.ext.loggly.io.IoUtils;
41 |
42 | /**
43 | *
44 | * Logback batch appender for Loggly HTTP API.
45 | *
46 | * Note:Loggly's Syslog API is much more scalable than the HTTP API which should mostly be used in
47 | * low-volume or non-production systems. The HTTP API can be very convenient to workaround firewalls.
48 | * If the {@link LogglyBatchAppender} saturates and discards log messages, the following warning message is
49 | * appended to both Loggly and {@link System#err}:
50 | * "$date - OutputStream is full, discard previous logs
"
51 | * Configuration settings
52 | *
53 | *
54 | * Property Name |
55 | * Type |
56 | * Description |
57 | *
58 | *
59 | * inputKey |
60 | * String |
61 | * Loggly input key. "inputKey " or endpointUrl is required. Sample
62 | * "12345678-90ab-cdef-1234-567890abcdef " |
63 | *
64 | *
65 | * endpointUrl |
66 | * String |
67 | * Loggly HTTP API endpoint URL. "inputKey " or endpointUrl is required. Sample:
68 | * "https://logs.loggly.com/inputs/12345678-90ab-cdef-1234-567890abcdef " |
69 | *
70 | *
71 | * pattern |
72 | * String |
73 | * Pattern used for Loggly log messages. Default value is:
74 | * %d{"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",UTC} %-5level [%thread] %logger: %m%n . |
75 | *
76 | *
77 | * proxyHost |
78 | * String |
79 | * hostname of a proxy server. If blank, no proxy is used (See {@link URL#openConnection(java.net.Proxy)}. |
80 | *
81 | *
82 | * proxyPort |
83 | * int |
84 | * port of a proxy server. Must be a valid int but is ignored if proxyHost is blank or null. |
85 | *
86 | *
87 | * jmxMonitoring |
88 | * boolean |
89 | * Enable registration of a monitoring MBean named
90 | * "ch.qos.logback:type=LogglyBatchAppender,name=LogglyBatchAppender@#hashcode# ". Default: true . |
91 | *
92 | *
93 | * maxNumberOfBuckets |
94 | * int |
95 | * Max number of buckets of in the byte buffer. Default value: 8 . |
96 | *
97 | *
98 | * maxBucketSizeInKilobytes |
99 | * int |
100 | * Max size of each bucket. Default value: 1024 Kilobytes (1MB). |
101 | *
102 | *
103 | * flushIntervalInSeconds |
104 | * int |
105 | * Interval of the buffer flush to Loggly API. Default value: 3 . |
106 | *
107 | *
108 | * connReadTimeoutSeconds |
109 | * int |
110 | * How Long the HTTP Connection will wait on reads. Default value: 1 second. |
111 | *
112 | *
113 | * Default configuration consumes up to 8 buffers of 1024 Kilobytes (1MB) each, which seems very reasonable even for small JVMs.
114 | * If logs are discarded, try first to shorten the flushIntervalInSeconds
parameter to "2s" or event "1s".
115 | *
116 | * Configuration Sample
117 | *
118 | * <configuration scan="true" scanPeriod="30 seconds" debug="true">
119 | * <if condition='isDefined("logback.loggly.inputKey")'>
120 | * <then>
121 | * <appender name="loggly" class="ch.qos.logback.ext.loggly.LogglyBatchAppender">
122 | * <inputKey>${logback.loggly.inputKey}</inputKey>
123 | * <pattern>%d{yyyy/MM/dd HH:mm:ss,SSS} [${HOSTNAME}] [%thread] %-5level %logger{36} - %m %throwable{5}%n</pattern>
124 | * <proxyHost>${logback.loggly.proxy.host:-}</proxyHost>
125 | * <proxyPort>${logback.loggly.proxy.port:-8080}</proxyPort>
126 | * <debug>${logback.loggly.debug:-false}</debug>
127 | * </appender>
128 | * <root level="WARN">
129 | * <appender-ref ref="loggly"/>
130 | * </root>
131 | * </then>
132 | * </if>
133 | * </configuration>
134 | *
135 | *
136 | *
137 | * Implementation decisions
138 | *
139 | * - Why buffer the generated log messages as bytes instead of using the
140 | * {@code ch.qos.logback.core.read.CyclicBufferAppender} and buffering the {@code ch.qos.logback.classic.spi.ILoggingEvent} ?
141 | * Because it is much easier to control the size in memory
142 | * -
143 | * Why buffer in a byte array instead of directly writing in a {@link BufferedOutputStream} on the {@link HttpURLConnection} ?
144 | * Because the Loggly API may not like such kind of streaming approach.
145 | *
146 | *
147 | *
148 | * @author Cyrille Le Clerc
149 | */
150 | public class LogglyBatchAppender extends AbstractLogglyAppender implements LogglyBatchAppenderMBean {
151 |
152 | public static final String ENDPOINT_URL_PATH = "bulk/";
153 |
154 | private boolean debug = false;
155 |
156 | private int flushIntervalInSeconds = 3;
157 |
158 | private DiscardingRollingOutputStream outputStream;
159 |
160 | protected final AtomicLong sendDurationInNanos = new AtomicLong();
161 |
162 | protected final AtomicLong sentBytes = new AtomicLong();
163 |
164 | protected final AtomicInteger sendSuccessCount = new AtomicInteger();
165 |
166 | protected final AtomicInteger sendExceptionCount = new AtomicInteger();
167 |
168 | private ScheduledExecutorService scheduledExecutor;
169 |
170 | private boolean jmxMonitoring = true;
171 |
172 | private MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
173 |
174 | private ObjectName registeredObjectName;
175 |
176 | private int maxNumberOfBuckets = 8;
177 |
178 | private int maxBucketSizeInKilobytes = 1024;
179 |
180 | private Charset charset = Charset.forName("UTF-8");
181 |
182 | /* Store Connection Read Timeout */
183 | private int connReadTimeoutSeconds = 1;
184 |
185 | @Override
186 | protected void append(E eventObject) {
187 | if (!isStarted()) {
188 | return;
189 | }
190 | String msg = this.layout.doLayout(eventObject);
191 |
192 | // Issue #21: Make sure messages end with new-line to delimit
193 | // individual log events within the batch sent to loggly.
194 | if (!msg.endsWith("\n")) {
195 | msg += "\n";
196 | }
197 |
198 | try {
199 | outputStream.write(msg.getBytes(charset));
200 | } catch (IOException e) {
201 | throw new RuntimeException(e);
202 | }
203 | }
204 |
205 | @Override
206 | public void start() {
207 |
208 | // OUTPUTSTREAM
209 | outputStream = new DiscardingRollingOutputStream(
210 | maxBucketSizeInKilobytes * 1024,
211 | maxNumberOfBuckets) {
212 | @Override
213 | protected void onBucketDiscard(ByteArrayOutputStream discardedBucket) {
214 | if (isDebug()) {
215 | addInfo("Discard bucket - " + getDebugInfo());
216 | }
217 | String s = new Timestamp(System.currentTimeMillis()) + " - OutputStream is full, discard previous logs" + LINE_SEPARATOR;
218 | try {
219 | getFilledBuckets().peekLast().write(s.getBytes(charset));
220 | addWarn(s);
221 | } catch (IOException e) {
222 | addWarn("Exception appending warning message '" + s + "'", e);
223 | }
224 | }
225 |
226 | @Override
227 | protected void onBucketRoll(ByteArrayOutputStream rolledBucket) {
228 | if (isDebug()) {
229 | addInfo("Roll bucket - " + getDebugInfo());
230 | }
231 | }
232 |
233 | };
234 |
235 | // SCHEDULER
236 | ThreadFactory threadFactory = new ThreadFactory() {
237 | @Override
238 | public Thread newThread(Runnable r) {
239 | Thread thread = Executors.defaultThreadFactory().newThread(r);
240 | thread.setName("logback-loggly-appender");
241 | thread.setDaemon(true);
242 | return thread;
243 | }
244 | };
245 | scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
246 | scheduledExecutor.scheduleWithFixedDelay(new LogglyExporter(), flushIntervalInSeconds, flushIntervalInSeconds, TimeUnit.SECONDS);
247 |
248 | // MONITORING
249 | if (jmxMonitoring) {
250 | String objectName = "ch.qos.logback:type=LogglyBatchAppender,name=LogglyBatchAppender@" + System.identityHashCode(this);
251 | try {
252 | registeredObjectName = mbeanServer.registerMBean(this, new ObjectName(objectName)).getObjectName();
253 | } catch (Exception e) {
254 | addWarn("Exception registering mbean '" + objectName + "'", e);
255 | }
256 | }
257 |
258 | // super.setOutputStream() must be defined before calling super.start()
259 | super.start();
260 | }
261 |
262 | @Override
263 | public void stop() {
264 | scheduledExecutor.shutdown();
265 |
266 | processLogEntries();
267 |
268 | if (registeredObjectName != null) {
269 | try {
270 | mbeanServer.unregisterMBean(registeredObjectName);
271 | } catch (Exception e) {
272 | addWarn("Exception unRegistering mbean " + registeredObjectName, e);
273 | }
274 | }
275 |
276 | try {
277 | scheduledExecutor.awaitTermination(2 * this.flushIntervalInSeconds, TimeUnit.SECONDS);
278 | } catch (InterruptedException e) {
279 | addWarn("Exception waiting for termination of LogglyAppender scheduler", e);
280 | }
281 |
282 | // stop appender (ie close outputStream) after sending it to Loggly
283 | outputStream.close();
284 |
285 | super.stop();
286 | }
287 |
288 | /**
289 | * Send log entries to Loggly
290 | */
291 | @Override
292 | public void processLogEntries() {
293 | if (isDebug()) {
294 | addInfo("Process log entries - " + getDebugInfo());
295 | }
296 |
297 | outputStream.rollCurrentBucketIfNotEmpty();
298 | BlockingDeque filledBuckets = outputStream.getFilledBuckets();
299 |
300 | ByteArrayOutputStream bucket;
301 |
302 | while ((bucket = filledBuckets.poll()) != null) {
303 | try {
304 | InputStream in = new ByteArrayInputStream(bucket.toByteArray());
305 | processLogEntries(in);
306 | } catch (Exception e) {
307 | addWarn("Internal error", e);
308 | }
309 | outputStream.recycleBucket(bucket);
310 | }
311 | }
312 |
313 | /**
314 | * Creates a configured HTTP connection to a URL (does not open the
315 | * connection)
316 | *
317 | * @param url target URL
318 | * @return the newly created HTTP connection
319 | * @throws IOException connection error
320 | */
321 | protected HttpURLConnection getHttpConnection(URL url) throws IOException {
322 | HttpURLConnection conn;
323 | if (proxy == null) {
324 | conn = (HttpURLConnection) url.openConnection();
325 | } else {
326 | conn = (HttpURLConnection) url.openConnection(proxy);
327 | }
328 |
329 | conn.setDoOutput(true);
330 | conn.setDoInput(true);
331 | conn.setRequestProperty("Content-Type", layout.getContentType() + "; charset=" + charset.name());
332 | conn.setRequestMethod("POST");
333 | conn.setReadTimeout(getHttpReadTimeoutInMillis());
334 | return conn;
335 | }
336 |
337 | /**
338 | * Send log entries to Loggly
339 | * @param in log input stream
340 | */
341 | protected void processLogEntries(InputStream in) {
342 | long nanosBefore = System.nanoTime();
343 | try {
344 |
345 | HttpURLConnection conn = getHttpConnection(new URL(endpointUrl));
346 | /* Set connection Read Timeout */
347 | conn.setReadTimeout(connReadTimeoutSeconds*1000);
348 | BufferedOutputStream out = new BufferedOutputStream(conn.getOutputStream());
349 |
350 | long len = IoUtils.copy(in, out);
351 | sentBytes.addAndGet(len);
352 |
353 | out.flush();
354 | out.close();
355 |
356 | int responseCode = conn.getResponseCode();
357 | String response = super.readResponseBody(conn.getInputStream());
358 | switch (responseCode) {
359 | case HttpURLConnection.HTTP_OK:
360 | case HttpURLConnection.HTTP_ACCEPTED:
361 | sendSuccessCount.incrementAndGet();
362 | break;
363 | default:
364 | sendExceptionCount.incrementAndGet();
365 | addError("LogglyAppender server-side exception: " + responseCode + ": " + response);
366 | }
367 | // force url connection recycling
368 | try {
369 | conn.getInputStream().close();
370 | conn.disconnect();
371 | } catch (Exception e) {
372 | // swallow exception
373 | }
374 | } catch (Exception e) {
375 | sendExceptionCount.incrementAndGet();
376 | addError("LogglyAppender client-side exception", e);
377 | } finally {
378 | sendDurationInNanos.addAndGet(System.nanoTime() - nanosBefore);
379 | }
380 | }
381 |
382 | public int getFlushIntervalInSeconds() {
383 | return flushIntervalInSeconds;
384 | }
385 |
386 | public void setFlushIntervalInSeconds(int flushIntervalInSeconds) {
387 | this.flushIntervalInSeconds = flushIntervalInSeconds;
388 | }
389 |
390 | @Override
391 | public long getSentBytes() {
392 | return sentBytes.get();
393 | }
394 |
395 | @Override
396 | public long getSendDurationInNanos() {
397 | return sendDurationInNanos.get();
398 | }
399 |
400 | @Override
401 | public int getSendSuccessCount() {
402 | return sendSuccessCount.get();
403 | }
404 |
405 | @Override
406 | public int getSendExceptionCount() {
407 | return sendExceptionCount.get();
408 | }
409 |
410 | @Override
411 | public int getDiscardedBucketsCount() {
412 | return outputStream.getDiscardedBucketCount();
413 | }
414 |
415 | @Override
416 | public long getCurrentLogEntriesBufferSizeInBytes() {
417 | return outputStream.getCurrentOutputStreamSize();
418 | }
419 |
420 | public void setDebug(boolean debug) {
421 | this.debug = debug;
422 | }
423 |
424 | public boolean isDebug() {
425 | return debug;
426 | }
427 |
428 | public void setJmxMonitoring(boolean jmxMonitoring) {
429 | this.jmxMonitoring = jmxMonitoring;
430 | }
431 |
432 | public void setMbeanServer(MBeanServer mbeanServer) {
433 | this.mbeanServer = mbeanServer;
434 | }
435 |
436 | public void setMaxNumberOfBuckets(int maxNumberOfBuckets) {
437 | this.maxNumberOfBuckets = maxNumberOfBuckets;
438 | }
439 |
440 | public void setMaxBucketSizeInKilobytes(int maxBucketSizeInKilobytes) {
441 | this.maxBucketSizeInKilobytes = maxBucketSizeInKilobytes;
442 | }
443 |
444 | /**
445 | * set method for Logback to allow Connection Read Timeout to be exposed
446 | */
447 | public void setConnReadTimeoutSeconds(int connReadTimeoutSeconds) {
448 | this.connReadTimeoutSeconds = connReadTimeoutSeconds;
449 | }
450 |
451 | private String getDebugInfo() {
452 | return "{" +
453 | "sendDurationInMillis=" + TimeUnit.MILLISECONDS.convert(sendDurationInNanos.get(), TimeUnit.NANOSECONDS) +
454 | ", sendSuccessCount=" + sendSuccessCount +
455 | ", sendExceptionCount=" + sendExceptionCount +
456 | ", sentBytes=" + sentBytes +
457 | ", discardedBucketsCount=" + getDiscardedBucketsCount() +
458 | ", currentLogEntriesBufferSizeInBytes=" + getCurrentLogEntriesBufferSizeInBytes() +
459 | '}';
460 | }
461 |
462 | public class LogglyExporter implements Runnable {
463 | @Override
464 | public void run() {
465 | try {
466 | processLogEntries();
467 | } catch (Exception e) {
468 | addWarn("Exception processing log entries", e);
469 | }
470 | }
471 | }
472 |
473 | @Override
474 | protected String getEndpointPrefix() {
475 | return ENDPOINT_URL_PATH;
476 | }
477 | }
478 |
--------------------------------------------------------------------------------
/loggly/src/main/java/ch/qos/logback/ext/loggly/LogglyBatchAppenderMBean.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly;
17 |
18 | /**
19 | * JMX Mbean interface for the {@link LogglyBatchAppender}.
20 | *
21 | * @author Cyrille Le Clerc
22 | */
23 | public interface LogglyBatchAppenderMBean {
24 |
25 | void processLogEntries();
26 |
27 | /**
28 | * Number of bytes sent to Loggly.
29 | */
30 | long getSentBytes();
31 |
32 | /**
33 | * Duration spent sending logs to Loggly.
34 | */
35 | long getSendDurationInNanos();
36 |
37 | /**
38 | * Number of successful invocations to Loggly's send logs API.
39 | */
40 | int getSendSuccessCount();
41 |
42 | /**
43 | * Number of failing invocations to Loggly's send logs API.
44 | */
45 | int getSendExceptionCount();
46 |
47 | /**
48 | * Number of discarded buckets
49 | */
50 | int getDiscardedBucketsCount();
51 |
52 | /**
53 | * Size in bytes of the log entries that have not yet been sent to Loggly.
54 | */
55 | long getCurrentLogEntriesBufferSizeInBytes();
56 |
57 | boolean isDebug();
58 |
59 | /**
60 | * Enable debugging
61 | */
62 | void setDebug(boolean debug);
63 | }
64 |
--------------------------------------------------------------------------------
/loggly/src/main/java/ch/qos/logback/ext/loggly/io/DiscardingRollingOutputStream.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly.io;
17 |
18 | import java.io.ByteArrayOutputStream;
19 | import java.io.IOException;
20 | import java.io.OutputStream;
21 | import java.util.concurrent.BlockingDeque;
22 | import java.util.concurrent.ConcurrentLinkedQueue;
23 | import java.util.concurrent.LinkedBlockingDeque;
24 | import java.util.concurrent.atomic.AtomicInteger;
25 | import java.util.concurrent.locks.ReentrantLock;
26 |
27 | /**
28 | *
29 | * Capped in-memory {@linkplain OutputStream} composed of a chain of {@linkplain ByteArrayOutputStream} called 'buckets'.
30 | *
31 | *
32 | * Each 'bucket' is limited in size (see {@link #maxBucketSizeInBytes}) and the total size of the {@linkplain OutputStream}
33 | * is bounded thanks to a discarding policy. An external component is expected to consume the filled buckets thanks to
34 | * {@link #getFilledBuckets()}.
35 | *
36 | *
37 | * Implementation decisions:
38 | *
39 | *
40 | * - Why in-memory without offload on disk: offload on disk was possible with Google Guava's
41 | *
FileBackedOutputStream
but had the drawback to introduce a dependency. Loggly batch appender use case
42 | * should be OK with a pure in-memory approach.
43 | *
44 | *
45 | * @author Cyrille Le Clerc
46 | */
47 | public class DiscardingRollingOutputStream extends OutputStream {
48 |
49 | public static final String LINE_SEPARATOR = System.getProperty("line.separator");
50 |
51 | private ByteArrayOutputStream currentBucket;
52 |
53 | private final ReentrantLock currentBucketLock = new ReentrantLock();
54 |
55 | private final BlockingDeque filledBuckets;
56 |
57 | private final ConcurrentLinkedQueue recycledBucketPool;
58 |
59 | private long maxBucketSizeInBytes;
60 |
61 | private final AtomicInteger discardedBucketCount = new AtomicInteger();
62 |
63 | /**
64 | * @param maxBucketSizeInBytes maximum byte size of each bucket
65 | * @param maxBucketCount maximum number of buckets
66 | */
67 | public DiscardingRollingOutputStream(int maxBucketSizeInBytes, int maxBucketCount) {
68 | if (maxBucketCount < 2) {
69 | throw new IllegalArgumentException("'maxBucketCount' must be >1");
70 | }
71 |
72 | this.maxBucketSizeInBytes = maxBucketSizeInBytes;
73 | this.filledBuckets = new LinkedBlockingDeque(maxBucketCount);
74 |
75 | this.recycledBucketPool = new ConcurrentLinkedQueue();
76 | this.currentBucket = newBucket();
77 | }
78 |
79 |
80 | @Override
81 | public void write(int b) throws IOException {
82 | currentBucketLock.lock();
83 | try {
84 | currentBucket.write(b);
85 | rollCurrentBucketIfNeeded();
86 | } finally {
87 | currentBucketLock.unlock();
88 | }
89 | }
90 |
91 | @Override
92 | public void write(byte[] b) throws IOException {
93 | currentBucketLock.lock();
94 | try {
95 | currentBucket.write(b);
96 | rollCurrentBucketIfNeeded();
97 | } finally {
98 | currentBucketLock.unlock();
99 | }
100 | }
101 |
102 | @Override
103 | public void write(byte[] b, int off, int len) throws IOException {
104 | currentBucketLock.lock();
105 | try {
106 | currentBucket.write(b, off, len);
107 | rollCurrentBucketIfNeeded();
108 | } finally {
109 | currentBucketLock.unlock();
110 | }
111 | }
112 |
113 | @Override
114 | public void flush() throws IOException {
115 | currentBucketLock.lock();
116 | try {
117 | currentBucket.flush();
118 | } finally {
119 | currentBucketLock.unlock();
120 | }
121 | }
122 |
123 | /**
124 | * Close all the underlying buckets (current bucket, filled buckets and buckets from the recycled buckets pool).
125 | */
126 | @Override
127 | public void close() {
128 | // no-op as ByteArrayOutputStream#close() is no op
129 | }
130 |
131 | /**
132 | * Roll current bucket if size threshold has been reached.
133 | */
134 | private void rollCurrentBucketIfNeeded() {
135 | if (currentBucket.size() < maxBucketSizeInBytes) {
136 | return;
137 | }
138 | rollCurrentBucket();
139 | }
140 |
141 | /**
142 | * Roll current bucket if size threshold has been reached.
143 | */
144 | public void rollCurrentBucketIfNotEmpty() {
145 | if (currentBucket.size() == 0) {
146 | return;
147 | }
148 | rollCurrentBucket();
149 | }
150 |
151 | /**
152 | * Moves the current active bucket to the list of filled buckets and defines a new one.
153 | *
154 | * The new active bucket is reused from the {@link #recycledBucketPool} pool if one is available or recreated.
155 | */
156 | public void rollCurrentBucket() {
157 | currentBucketLock.lock();
158 | try {
159 | boolean offered = filledBuckets.offer(currentBucket);
160 | if (offered) {
161 | onBucketRoll(currentBucket);
162 | } else {
163 | onBucketDiscard(currentBucket);
164 | discardedBucketCount.incrementAndGet();
165 | }
166 |
167 | currentBucket = newBucket();
168 | } finally {
169 | currentBucketLock.unlock();
170 | }
171 | }
172 |
173 | /**
174 | * Designed for extension.
175 | *
176 | * @param discardedBucket the discarded bucket
177 | */
178 | protected void onBucketDiscard(ByteArrayOutputStream discardedBucket) {
179 |
180 | }
181 |
182 | /**
183 | * The rolled bucket. Designed for extension.
184 | *
185 | * @param rolledBucket the discarded bucket
186 | */
187 | protected void onBucketRoll(ByteArrayOutputStream rolledBucket) {
188 |
189 | }
190 |
191 | /**
192 | * Get a new bucket from the {@link #recycledBucketPool} or instantiate a new one if none available
193 | * in the free bucket pool.
194 | *
195 | * @return the bucket ready to use
196 | */
197 | protected ByteArrayOutputStream newBucket() {
198 | ByteArrayOutputStream bucket = recycledBucketPool.poll();
199 | if (bucket == null) {
200 | bucket = new ByteArrayOutputStream();
201 | }
202 | return bucket;
203 | }
204 |
205 | /**
206 | * Returns the given bucket to the pool of free buckets.
207 | *
208 | * @param bucket the bucket to recycle
209 | */
210 | public void recycleBucket(ByteArrayOutputStream bucket) {
211 | bucket.reset();
212 | recycledBucketPool.offer(bucket);
213 | }
214 |
215 | /**
216 | * Return the filled buckets
217 | */
218 | public BlockingDeque getFilledBuckets() {
219 | return filledBuckets;
220 | }
221 |
222 | /**
223 | * @return Number of discarded buckets. Monitoring oriented metric.
224 | */
225 | public int getDiscardedBucketCount() {
226 | return discardedBucketCount.get();
227 | }
228 |
229 | public long getCurrentOutputStreamSize() {
230 | long sizeInBytes = 0;
231 | for (ByteArrayOutputStream bucket : filledBuckets) {
232 | sizeInBytes += bucket.size();
233 | }
234 | sizeInBytes += currentBucket.size();
235 | return sizeInBytes;
236 | }
237 |
238 | @Override
239 | public String toString() {
240 | return "DiscardingRollingOutputStream{" +
241 | "currentBucket.bytesWritten=" + currentBucket.size() +
242 | ", filledBuckets.size=" + filledBuckets.size() +
243 | ", discardedBucketCount=" + discardedBucketCount +
244 | ", recycledBucketPool.size=" + recycledBucketPool.size() +
245 | '}';
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/loggly/src/main/java/ch/qos/logback/ext/loggly/io/IoUtils.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly.io;
17 |
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.io.OutputStream;
21 |
22 | /**
23 | * @author Cyrille Le Clerc
24 | */
25 | public class IoUtils {
26 |
27 | public static long copy(InputStream in, OutputStream out) throws IOException {
28 | byte[] buffer = new byte[1024]; // 1k
29 | long totalSize = 0;
30 | while (true) {
31 | int size = in.read(buffer);
32 | if (size == -1) {
33 | return totalSize;
34 | }
35 | out.write(buffer, 0, size);
36 | totalSize += size;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/loggly/src/test/java/ch/qos/logback/ext/loggly/HttpTestServer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly;
17 |
18 | import java.io.IOException;
19 | import java.io.PrintStream;
20 | import java.net.InetSocketAddress;
21 | import java.net.SocketAddress;
22 | import java.nio.ByteBuffer;
23 | import java.util.concurrent.atomic.AtomicInteger;
24 |
25 | import org.simpleframework.http.Request;
26 | import org.simpleframework.http.Response;
27 | import org.simpleframework.http.core.Container;
28 | import org.simpleframework.http.core.ContainerServer;
29 | import org.simpleframework.transport.Server;
30 | import org.simpleframework.transport.connect.Connection;
31 | import org.simpleframework.transport.connect.SocketConnection;
32 |
33 | /**
34 | * HTTP test server that tracks the number of requests received
35 | */
36 | public class HttpTestServer implements Container {
37 | static private final int MAX_RXBUF_SIZE = 2048;
38 | private int port;
39 | private Connection connection;
40 | private Server server;
41 | AtomicInteger numRequests;
42 |
43 | /**
44 | * Initializes the HTTP server
45 | * @param port
46 | */
47 | public HttpTestServer(int port) {
48 | this.port = port;
49 | numRequests = new AtomicInteger(0);
50 | }
51 |
52 | /**
53 | * Opens the HTTP server
54 | * @throws IOException
55 | */
56 | public void start() throws IOException {
57 | stop();
58 | this.server = new ContainerServer(this);
59 | this.connection = new SocketConnection(server);
60 | SocketAddress address = new InetSocketAddress(this.port);
61 | this.connection.connect(address);
62 | }
63 |
64 | /**
65 | * Closes the HTTP server
66 | */
67 | public void stop() {
68 | if (this.connection != null) {
69 | try {
70 | this.connection.close();
71 | } catch (IOException e) {
72 | }
73 | }
74 | }
75 |
76 | /**
77 | * Gets the number of requests received
78 | * @return the request count
79 | */
80 | public int requestCount() {
81 | return numRequests.get();
82 | }
83 |
84 | /**
85 | * Resets the request count
86 | */
87 | public void clearRequests() {
88 | numRequests.set(0);
89 | }
90 |
91 | /**
92 | * Waits indefinitely for the specified number of requests to be received
93 | * @param count the number of received requests to wait for
94 | */
95 | public void waitForRequests(int count) {
96 | waitForRequests(count, 0, 0);
97 | }
98 |
99 | /**
100 | * Waits for the specified number of requests to be received
101 | * @param count the number of received requests to wait for
102 | * @param interval the wait time between polls, checking the receive count;
103 | * use 0 to wait indefinitely
104 | * @param maxPolls the maximum number of polls; use 0 for no limit
105 | */
106 | public void waitForRequests(int count, long interval, int maxPolls) {
107 | synchronized (this) {
108 | while (requestCount() < count) {
109 | System.out.println("requests " + requestCount() + "/" + count);
110 | if (maxPolls > 0 && maxPolls-- > 0) {
111 | break;
112 | }
113 | try {
114 | this.wait(interval);
115 | } catch (InterruptedException e) {
116 | }
117 | }
118 | System.out.println("requests " + requestCount() + "/" + count);
119 | }
120 | }
121 |
122 | /**
123 | * Handles incoming HTTP requests by responding with the
124 | * message index and size of the received message.
125 | * @see org.simpleframework.http.core.Container#handle(org.simpleframework.http.Request, org.simpleframework.http.Response)
126 | */
127 | @Override
128 | public void handle(Request request, Response response) {
129 | try {
130 | PrintStream body = response.getPrintStream();
131 | long time = System.currentTimeMillis();
132 |
133 | response.setValue("Content-Type", "text/html");
134 | response.setValue("Server", "HttpTestServer/1.0 (Simple 4.0)");
135 | response.setDate("Date", time);
136 | response.setDate("Last-Modified", time);
137 |
138 | ByteBuffer buf = ByteBuffer.allocate(MAX_RXBUF_SIZE);
139 | int len = request.getByteChannel().read(buf);
140 | int count = numRequests.incrementAndGet();
141 |
142 | // warn if RX buffer exceeded (message truncated)
143 | String warning = "";
144 | if (len > MAX_RXBUF_SIZE) {
145 | warning = "(" + (len - MAX_RXBUF_SIZE) + " bytes truncated)";
146 | }
147 |
148 | body.println("Request #" + count + "\n" + len + " bytes read" + warning);
149 | body.close();
150 |
151 | synchronized (this) {
152 | notify();
153 | }
154 | } catch (Exception e) {
155 | e.printStackTrace();
156 | }
157 | }
158 | }
--------------------------------------------------------------------------------
/loggly/src/test/java/ch/qos/logback/ext/loggly/LogglyBatchAppenderTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly;
17 |
18 | import java.io.IOException;
19 | import java.net.InetAddress;
20 | import java.net.UnknownHostException;
21 |
22 | import static org.junit.Assert.assertTrue;
23 | import static org.junit.Assert.assertEquals;
24 |
25 | import org.junit.AfterClass;
26 | import org.junit.Before;
27 | import org.junit.BeforeClass;
28 | import org.junit.Ignore;
29 | import org.junit.Test;
30 |
31 | import ch.qos.logback.classic.LoggerContext;
32 | import ch.qos.logback.core.layout.EchoLayout;
33 |
34 | /**
35 | * Tests the LogglyBatchAppender
36 | */
37 | @Ignore
38 | public class LogglyBatchAppenderTest {
39 |
40 | static private final int PORT = 10800;
41 | static private final int MAX_BUCKETS = 4;
42 | static private final int BUCKET_KB_SIZE = 1;
43 | static private final int MSG_SIZE = BUCKET_KB_SIZE * 1024;
44 | static private HttpTestServer httpServer;
45 | static private LogglyBatchAppender appender;
46 | static private LoggerContext context;
47 |
48 | /**
49 | * Starts the HTTP test server, initializes the appender,
50 | * and creates a context with a status listener
51 | * @throws IOException
52 | */
53 | @BeforeClass
54 | static public void beforeClass() throws IOException {
55 | httpServer = new HttpTestServer(PORT);
56 | httpServer.start();
57 | context = new LoggerContext();
58 | }
59 |
60 | /**
61 | * Shuts down the HTTP test server
62 | */
63 | @AfterClass
64 | static public void afterClass() {
65 | httpServer.stop();
66 | appender.stop();
67 | }
68 |
69 | @Before
70 | public void before() throws UnknownHostException {
71 | httpServer.clearRequests();
72 |
73 | appender = new LogglyBatchAppender();
74 | appender.setContext(context);
75 | appender.setEndpointUrl("http://" + InetAddress.getLocalHost().getHostAddress() + ":" + PORT + "/");
76 |
77 | appender.setLayout(new EchoLayout());
78 | appender.setDebug(true);
79 | appender.setMaxBucketSizeInKilobytes(BUCKET_KB_SIZE);
80 | appender.setMaxNumberOfBuckets(MAX_BUCKETS);
81 | appender.start();
82 | }
83 |
84 | @Test
85 | public void starts() {
86 | assertTrue(appender.isStarted());
87 | }
88 |
89 | private void appendFullBuckets(int count) {
90 | for (int i = 0; i < count; i++) {
91 | appender.doAppend(new String(new char[MSG_SIZE]).replace("\0", "X"));
92 | }
93 | }
94 |
95 | @Test(timeout = 180000)
96 | public void sendsOnlyWhenMaxBucketsFull() {
97 | // assert nothing yet sent/received
98 | assertEquals(0, appender.getSendSuccessCount());
99 | assertEquals(0, httpServer.requestCount());
100 |
101 | // send stuff and wait for it to be received
102 | appendFullBuckets(MAX_BUCKETS);
103 | httpServer.waitForRequests(MAX_BUCKETS);
104 |
105 | // assert stuff sent/received
106 | assertEquals(MAX_BUCKETS, appender.getSendSuccessCount());
107 | assertEquals(MAX_BUCKETS, httpServer.requestCount());
108 | }
109 |
110 | @Test(timeout = 180000)
111 | public void excessBucketsGetDiscarded() {
112 | // assert nothing yet discarded (because nothing is yet sent)
113 | assertEquals(0, appender.getDiscardedBucketsCount());
114 |
115 | // send stuff and wait for it to be received
116 | final int NUM_MSGS = 40;
117 | appendFullBuckets(NUM_MSGS);
118 | httpServer.waitForRequests(MAX_BUCKETS);
119 |
120 | // assert excess buckets (those > MAX_BUCKETS) were discarded
121 | assertEquals(NUM_MSGS - MAX_BUCKETS, appender.getDiscardedBucketsCount());
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/loggly/src/test/java/ch/qos/logback/ext/loggly/LogglyHttpAppenderIntegratedTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly;
17 |
18 | import ch.qos.logback.core.layout.EchoLayout;
19 | import ch.qos.logback.ext.loggly.io.IoUtils;
20 | import de.svenjacobs.loremipsum.LoremIpsum;
21 |
22 | import java.io.FileOutputStream;
23 | import java.io.IOException;
24 | import java.io.InputStream;
25 | import java.io.OutputStream;
26 | import java.sql.Timestamp;
27 | import java.text.SimpleDateFormat;
28 | import java.util.Date;
29 | import java.util.Random;
30 | import java.util.concurrent.TimeUnit;
31 |
32 | import static org.junit.Assert.*;
33 |
34 | /**
35 | * @author Cyrille Le Clerc
36 | */
37 | public class LogglyHttpAppenderIntegratedTest {
38 |
39 | public static void main(String[] args) throws Exception {
40 |
41 | Random random = new Random();
42 | LoremIpsum loremIpsum = new LoremIpsum();
43 |
44 | String file = "/tmp/loggly-appender-test-" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".log";
45 | System.out.println("Generate " + file);
46 | final OutputStream out = new FileOutputStream(file);
47 |
48 | LogglyBatchAppender appender = new LogglyBatchAppender() {
49 | @Override
50 | protected void processLogEntries(InputStream in) {
51 | // super.processLogEntries(in);
52 | try {
53 | IoUtils.copy(in, out);
54 | } catch (IOException e) {
55 | e.printStackTrace();
56 | }
57 | }
58 | };
59 | appender.setInputKey("YOUR LOGGLY INPUT KEY");
60 | appender.setLayout(new EchoLayout());
61 | appender.setDebug(true);
62 |
63 | // Start
64 | appender.start();
65 |
66 | assertTrue("Appender failed to start", appender.isStarted());
67 |
68 | appender.doAppend("# Test " + new Timestamp(System.currentTimeMillis()));
69 |
70 | for (int i = 0; i < 100000; i++) {
71 | appender.doAppend(i + " -- " + new Timestamp(System.currentTimeMillis()) + " - " + loremIpsum.getWords(random.nextInt(50), random.nextInt(50)));
72 | TimeUnit.MILLISECONDS.sleep(random.nextInt(30));
73 | if (i % 100 == 0) {
74 | System.out.println(i + " - " + appender);
75 | }
76 | }
77 | // stop
78 | appender.stop();
79 |
80 | out.close();
81 | System.out.println(appender);
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/loggly/src/test/java/ch/qos/logback/ext/loggly/LogglySendOnlyWhenMaxBucketsFullTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.loggly;
17 |
18 | import static org.junit.Assert.assertEquals;
19 | import static org.mockito.Mockito.*;
20 |
21 | import java.io.BufferedInputStream;
22 | import java.io.BufferedOutputStream;
23 | import java.io.ByteArrayInputStream;
24 | import java.io.ByteArrayOutputStream;
25 | import java.io.IOException;
26 | import java.io.InputStream;
27 | import java.net.HttpURLConnection;
28 | import java.net.InetAddress;
29 | import java.net.URL;
30 | import java.net.UnknownHostException;
31 |
32 | import org.junit.After;
33 | import org.junit.AfterClass;
34 | import org.junit.Before;
35 | import org.junit.BeforeClass;
36 | import org.junit.Test;
37 | import org.junit.runner.RunWith;
38 | import org.mockito.Mock;
39 | import org.mockito.runners.MockitoJUnitRunner;
40 |
41 | import ch.qos.logback.classic.LoggerContext;
42 | import ch.qos.logback.core.layout.EchoLayout;
43 |
44 | @RunWith(MockitoJUnitRunner.class)
45 | public class LogglySendOnlyWhenMaxBucketsFullTest {
46 | static private final int PORT = 10800;
47 | static private final int MAX_BUCKETS = 4;
48 | static private final int BUCKET_KB_SIZE = 1;
49 | static private final int MSG_SIZE = BUCKET_KB_SIZE * 1024;
50 | static private LogglyBatchAppender appender;
51 | static private LoggerContext context;
52 | private ByteArrayOutputStream byteOutputStream;
53 | private ByteArrayInputStream byteInputStream;
54 |
55 | @Mock
56 | HttpURLConnection connection;
57 |
58 | /**
59 | * Creates a context with a status listener
60 | */
61 | @BeforeClass
62 | static public void beforeClass() {
63 | context = new LoggerContext();
64 | }
65 |
66 | /**
67 | * Shuts down the appender's processing thread
68 | */
69 | @AfterClass
70 | static public void afterClass() {
71 | appender.stop();
72 | }
73 |
74 | @Before
75 | public void before() throws UnknownHostException {
76 | byteOutputStream = new ByteArrayOutputStream();
77 | byteInputStream = new ByteArrayInputStream(new byte[0]);
78 |
79 | appender = new LogglyBatchAppenderWithMockConnection();
80 | appender.setContext(context);
81 | appender.setEndpointUrl("http://" + InetAddress.getLocalHost().getHostAddress() + ":" + PORT + "/");
82 |
83 | appender.setLayout(new EchoLayout());
84 | appender.setDebug(true);
85 | appender.setMaxBucketSizeInKilobytes(BUCKET_KB_SIZE);
86 | appender.setMaxNumberOfBuckets(MAX_BUCKETS);
87 | appender.start();
88 |
89 | assertNoTrafficYet();
90 |
91 | }
92 |
93 | @After
94 | public void after() {
95 | System.out.println("BYTES ------>");
96 | System.out.println(byteOutputStream.toString());
97 | }
98 |
99 | @Test
100 | public void successCountMatchesSentCount() throws Exception {
101 | sendMessagesAndConfirmRx(MAX_BUCKETS);
102 | assertEquals(MAX_BUCKETS, appender.getSendSuccessCount());
103 | }
104 |
105 | @Test
106 | public void rxCountMatchesSentCount() throws Exception {
107 | sendMessagesAndConfirmRx(MAX_BUCKETS);
108 | assertEquals(MAX_BUCKETS, getRxMessageCount());
109 | }
110 |
111 | /**
112 | * Mirrors LogglyBatchAppender but uses a mock HTTP connection to
113 | * feed byte streams that we can control. Also calls notifyAll()
114 | * after log entries are processed.
115 | */
116 | private class LogglyBatchAppenderWithMockConnection extends LogglyBatchAppender {
117 |
118 | @Override
119 | protected HttpURLConnection getHttpConnection(URL url) throws IOException {
120 | when(connection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
121 | when(connection.getOutputStream()).thenReturn(new BufferedOutputStream(byteOutputStream));
122 | when(connection.getInputStream()).thenReturn(new BufferedInputStream(byteInputStream));
123 | return connection;
124 | }
125 |
126 | @Override
127 | protected void processLogEntries(InputStream in) {
128 | super.processLogEntries(in);
129 | synchronized(appender) {
130 | notifyAll();
131 | }
132 | }
133 | }
134 |
135 | /**
136 | * Verifies that no messages have been sent or received yet
137 | */
138 | private void assertNoTrafficYet() {
139 | assertEquals(0, appender.getSendSuccessCount());
140 | assertEquals(0, getRxMessageCount());
141 | }
142 |
143 | /**
144 | * Sends bucket-full messages to a remote server (which is just a mock connection)
145 | * and confirms delivery (by checking for method calls in the mock connection)
146 | *
147 | * @param count number of full buckets to send
148 | * @throws IOException
149 | * @throws InterruptedException
150 | */
151 | private void sendMessagesAndConfirmRx(int count) throws IOException, InterruptedException {
152 | appendFullBuckets(count);
153 |
154 | for (int i = 0; i < count; i++) {
155 | synchronized(appender) {
156 | appender.wait(10000);
157 | }
158 | }
159 |
160 | verify(connection, atLeast(MAX_BUCKETS)).getOutputStream();
161 | verify(connection, atLeast(MAX_BUCKETS)).getInputStream();
162 | verify(connection, atLeast(MAX_BUCKETS)).disconnect();
163 | }
164 |
165 | /**
166 | * Gets the number of messages "received"
167 | * @return the message count
168 | */
169 | private int getRxMessageCount() {
170 | String str = byteOutputStream.toString();
171 | return str.isEmpty() ? 0 : str.split("\n").length;
172 | }
173 |
174 | /**
175 | * Fills the appender's buckets with max-length messages
176 | * @param count number of buckets to fill
177 | */
178 | private void appendFullBuckets(int count) {
179 | for (int i = 0; i < count; i++) {
180 | appender.doAppend(i + ")" + new String(new char[MSG_SIZE]).replace("\0", "X"));
181 | }
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------
/loggly/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %throwable{5}%n
22 |
23 |
24 |
25 |
26 |
27 |
28 | %d{yyyy/MM/dd HH:mm:ss,SSS} [${HOSTNAME}] [%thread] %-5level %logger{36} - %msg %throwable{5}%n
29 |
30 | ${logback.loggly.inputKey:-}
31 | ${logback.loggly.proxy.host:-}
32 | ${logback.loggly.proxy.port:-8080}
33 | ${logback.loggly.debug:-false}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/scripts/deploysnapshot.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | user=${NEXUS_USERNAME}
4 | pass=${NEXUS_PASSWORD}
5 | [ -z "$user" ] && read -p "Nexus username: " user
6 | [ -z "$pass" ] && read -p "Nexus password: " -s pass
7 | echo ''
8 |
9 | ./gradlew uploadArchives -x test -x build -PNEXUS_USERNAME=${user} -PNEXUS_PASSWORD=${pass}
10 |
--------------------------------------------------------------------------------
/scripts/nexus.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash -e
2 |
3 | user=${NEXUS_USERNAME}
4 | pass=${NEXUS_PASSWORD}
5 | [ -z "$user" ] && read -p "Nexus username: " user
6 | [ -z "$pass" ] && read -p "Nexus password: " -s pass
7 | echo ''
8 |
9 | ./gradlew closeAndReleaseRepository -PnexusUsername=$user -PnexusPassword=$pass
10 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash -e
2 |
3 | . gradle.properties
4 | . local.properties
5 |
6 | version=${VERSION_NAME%*-SNAPSHOT}
7 | baseVersion=${version%*.*}
8 | nextBuild=$((${version##*.} + 1))
9 | nextVersion="${baseVersion}.${nextBuild}-SNAPSHOT"
10 |
11 | echo "Starting release for logback-ext-${version} ..."
12 |
13 | fail() {
14 | echo "error: $1" >&2
15 | exit 1
16 | }
17 |
18 | # Run Git integrity checks early (gradle-release-plugin does this
19 | # after we update the readme) to avoid premature push of new readme
20 | [[ "$(git rev-parse master)" != "$(git rev-parse origin/master)" ]] && fail "branches out of sync"
21 | [[ -n "$(git status -u -s)" ]] && fail "found unstaged changes"
22 |
23 | # gradle-release-plugin prompts for your Nexus credentials
24 | # with "Please specify username" (no mention of Nexus).
25 | # Use our own prompt to remind the user where they're
26 | # logging into to.
27 | user=${NEXUS_USERNAME}
28 | pass=${NEXUS_PASSWORD}
29 | [ -z "$user" ] && read -p "Nexus username: " user
30 | [ -z "$pass" ] && read -p "Nexus password: " -s pass
31 |
32 | #bintray_user=${BINTRAY_USER}
33 | #bintray_key=${BINTRAY_KEY}
34 | #[ -z "$bintray_user" ] && read -p "Bintray username: " bintray_user
35 | #[ -z "$bintray_key" ] && read -p "Bintray API key: " bintray_key
36 | #echo ''
37 |
38 | ./gradlew -Prelease.useAutomaticVersion=true \
39 | -Prelease.releaseVersion=${version} \
40 | -Prelease.newVersion=${nextVersion} \
41 | -Pversion=${version} \
42 | -PVERSION_NAME=${version} \
43 | -PNEXUS_USERNAME=${user} \
44 | -PNEXUS_PASSWORD=${pass} \
45 | -Ppush \
46 | -x test \
47 | clean \
48 | readme \
49 | release \
50 | uploadArchives
51 |
52 | #./gradlew -PBINTRAY_USER=${BINTRAY_USER} \
53 | # -PBINTRAY_KEY=${BINTRAY_KEY} \
54 | # bintrayUpload
55 |
56 | # To deploy archives without git transactions (tagging, etc.),
57 | # replace the `release` task above with `assembleRelease`.
58 |
59 | echo -e "\n\n"
60 |
61 | # FIXME: In test repo, this can't checkout 'gh-pages' -- no error provided
62 | #./gradlew uploadDocs
63 | echo TODO: upload javadocs to gh-pages with:
64 | echo scripts/deploydocs.sh ${version}
65 |
66 | # FIXME: hub is no longer able to find tagged releases for some reason.
67 | #hub release edit -m '' v_${version} -a build/logback-extensions-${version}.jar
68 | echo TODO: attach uber jar to release at:
69 | echo https://github.com/qos-ch/logback-extensions/releases/tag/v_${version}
70 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'logback-ext-parent'
2 | include ':logback-ext-loggly'
3 | include ':logback-ext-spring'
4 |
5 | project(':logback-ext-loggly').projectDir = "$rootDir/loggly" as File
6 | project(':logback-ext-spring').projectDir = "$rootDir/spring" as File
--------------------------------------------------------------------------------
/spring/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | description = POM_DESCRIPTION
3 | dependencies {
4 | compileOnly 'ch.qos.logback:logback-classic:1.2.3'
5 | compileOnly('org.springframework:spring-context:3.2.2.RELEASE') {
6 | exclude(module: 'commons-logging')
7 | }
8 | compileOnly('org.springframework:spring-web:3.2.2.RELEASE') {
9 | exclude(module: 'commons-logging')
10 | }
11 | compileOnly 'javax.servlet:servlet-api:2.5'
12 | }
13 |
--------------------------------------------------------------------------------
/spring/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=logback-ext-spring
2 | POM_ARTIFACT_ID=logback-ext-spring
3 | POM_PACKAGING=jar
4 | POM_DESCRIPTION="Logback Extensions :: Spring"
--------------------------------------------------------------------------------
/spring/src/main/java/ch/qos/logback/ext/spring/ApplicationContextHolder.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.spring;
17 |
18 | import org.springframework.beans.BeansException;
19 | import org.springframework.context.ApplicationContext;
20 | import org.springframework.context.ApplicationContextAware;
21 | import org.springframework.context.ApplicationListener;
22 | import org.springframework.context.event.ContextRefreshedEvent;
23 |
24 | /**
25 | * A special bean which may be defined in the Spring {@code ApplicationContext} to make the context available statically
26 | * to objects which, for whatever reason, cannot be wired up in Spring (for example, logging appenders which must be
27 | * defined in XML or properties files used to initialize the logging system).
28 | *
29 | * To use this holder, exactly one bean should be declared as follows:
30 | *
31 | * <bean class="ch.qos.logback.ext.spring.ApplicationContextHolder"/>
32 | *
33 | * Note that no ID is necessary because this holder should always be used via its static accessors, rather than being
34 | * injected. Any Spring bean which wishes to access the {@code ApplicationContext} should not rely on this holder; it
35 | * should simply implement {@code ApplicationContextAware}.
36 | *
37 | * WARNING: This object uses static memory to retain the ApplicationContext. This means this bean (and the
38 | * related configuration strategy) is only usable when no other Logback-enabled Spring applications exist in the same
39 | * JVM.
40 | *
41 | * @author Bryan Turner
42 | * @author Les Hazlewood
43 | * @since 0.1
44 | */
45 | public class ApplicationContextHolder implements ApplicationContextAware, ApplicationListener {
46 |
47 | private static ApplicationContext applicationContext;
48 | private static volatile boolean refreshed;
49 |
50 | @Override
51 | public void onApplicationEvent(ContextRefreshedEvent event) {
52 | refreshed = true;
53 | }
54 |
55 | /**
56 | * Ensures that the {@code ApplicationContext} has been set and that it has been refreshed. The refresh
57 | * event is sent when the context has completely finished starting up, meaning all beans have been created and
58 | * initialized successfully.
59 | *
60 | * This method has a loosely defined relationship with {@link #getApplicationContext()}. When this method returns
61 | * {@code true}, calling {@link #getApplicationContext()} is guaranteed to return a non-{@code null} context which
62 | * has been completely initialized. When this method returns {@code false}, {@link #getApplicationContext()} may
63 | * return {@code null}, or it may return a non-{@code null} context which is not yet completely initialized.
64 | *
65 | * @return {@code true} if the context has been set and refreshed; otherwise, {@code false}
66 | */
67 | public static boolean hasApplicationContext() {
68 | return (refreshed && applicationContext != null);
69 | }
70 |
71 | /**
72 | * Retrieves the {@code ApplicationContext} set when Spring created and initialized the holder bean. If the
73 | * holder has not been created (see the class documentation for details on how to wire up the holder), or if
74 | * the holder has not been initialized, this accessor may return {@code null}.
75 | *
76 | * As a general usage pattern, callers should wrap this method in a check for {@link #hasApplicationContext()}.
77 | * That ensures both that the context is set and also that it has fully initialized. Using a context which has
78 | * not been fully initialized can result in unexpected initialization behaviors for some beans. The most common
79 | * example of this behavior is receiving unproxied references to some beans, such as beans which were supposed
80 | * to have transactional semantics applied by AOP. By waiting for the context refresh event, the likelihood of
81 | * encountering such behavior is greatly reduced.
82 | *
83 | * @return the set context, or {@code null} if the holder bean has not been initialized
84 | */
85 | public static ApplicationContext getApplicationContext() {
86 | return applicationContext;
87 | }
88 |
89 | @Override
90 | public void setApplicationContext(ApplicationContext context) throws BeansException {
91 | applicationContext = context;
92 | }
93 |
94 | /**
95 | * Returns a flag indicating whether the {@code ApplicationContext} has been refreshed. Theoretically, it is
96 | * possible for this method to return {@code true} when {@link #hasApplicationContext()} returns {@code false},
97 | * but in practice that is very unlikely since the bean for the holder should have been created and initialized
98 | * before the refresh event was raised.
99 | *
100 | * @return {@code true} if the context refresh event has been received; otherwise, {@code false}
101 | */
102 | public static boolean isRefreshed() {
103 | return refreshed;
104 | }
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/spring/src/main/java/ch/qos/logback/ext/spring/DelegatingLogbackAppender.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.spring;
17 |
18 | import ch.qos.logback.classic.spi.ILoggingEvent;
19 | import ch.qos.logback.core.Appender;
20 | import ch.qos.logback.core.UnsynchronizedAppenderBase;
21 | import org.springframework.beans.factory.NoSuchBeanDefinitionException;
22 | import org.springframework.context.ApplicationContext;
23 |
24 | import java.util.List;
25 |
26 | /**
27 | * A Logback {@code Appender} implementation which delegates the actual appending to a named bean contained in a Spring
28 | * {@code ApplicationContext}.
29 | *
30 | * This appender is similar in spirit to Spring's {@code DelegatingFilterProxy}, which allows servlet filters to be
31 | * created and wired in the ApplicationContext and then accessed in the filter chain. As with the filter proxy, the
32 | * delegating appender uses its own name to find the target appender in the context.
33 | *
34 | * Because the logging framework is usually started before the Spring context, this appender supports caching for
35 | * {@code ILoggingEvent}s which are received before the {@code ApplicationContext} is available. This caching has
36 | * 3 possible modes:
37 | *
38 | * - off - Events are discarded until the {@code ApplicationContext} is available.
39 | * - on - Events are cached with strong references until the {@code ApplicationContext} is available, at
40 | * which time they will be forwarded to the delegate appender. In systems which produce substantial amounts of log
41 | * events while starting up the {@code ApplicationContext} this mode may result in heavy memory usage.
42 | * - soft - Events are wrapped in {@code SoftReference}s and cached until the {@code ApplicationContext}
43 | * is available. Memory pressure may cause the garbage collector to collect some or all of the cached events before
44 | * the {@code ApplicationContext} is available, so some or all events may be lost. However, in systems with heavy
45 | * logging, this mode may result in more efficient memory usage.
46 | *
47 | * Caching is {@code on} by default, so strong references will be used for all events.
48 | *
49 | * An example of how to use this appender in {@code logback.xml}:
50 | *
51 | * <appender name="appenderBeanName" class="ch.qos.logback.ext.spring.DelegatingLogbackAppender"/>
52 | *
53 | *
54 | * Or, if specifying a different cache mode, e.g.:
55 | *
56 | * <appender name="appenderBeanName" class="ch.qos.logback.ext.spring.DelegatingLogbackAppender">
57 | * <cacheMode>soft</cacheMode>
58 | * </appender>
59 | *
60 | * Using this appender requires that the {@link ApplicationContextHolder} be included in the {@code ApplicationContext}.
61 | *
62 | * @author Bryan Turner
63 | * @since 0.1
64 | */
65 | public class DelegatingLogbackAppender extends UnsynchronizedAppenderBase {
66 |
67 | private final Object lock;
68 |
69 | private String beanName;
70 | private ILoggingEventCache cache;
71 | private EventCacheMode cacheMode;
72 | private volatile Appender delegate;
73 |
74 | public DelegatingLogbackAppender() {
75 | cacheMode = EventCacheMode.ON;
76 | lock = new Object();
77 | }
78 |
79 | public void setCacheMode(String mode) {
80 | cacheMode = Enum.valueOf(EventCacheMode.class, mode.toUpperCase());
81 | }
82 |
83 | @Override
84 | public void start() {
85 | if (isStarted()) {
86 | return;
87 | }
88 |
89 | if (beanName == null || beanName.trim().isEmpty()) {
90 | if (name == null || name.trim().isEmpty()) {
91 | throw new IllegalStateException("A 'name' or 'beanName' is required for DelegatingLogbackAppender");
92 | }
93 | beanName = name;
94 | }
95 | cache = cacheMode.createCache();
96 |
97 | super.start();
98 | }
99 |
100 | @Override
101 | public void stop() {
102 | super.stop();
103 |
104 | if (cache != null) {
105 | cache = null;
106 | }
107 | if (delegate != null) {
108 | delegate.stop();
109 | delegate = null;
110 | }
111 | }
112 |
113 | @Override
114 | protected void append(ILoggingEvent event) {
115 | //Double-check locking here to optimize out the synchronization after the delegate is in place. This also has
116 | //the benefit of dealing with the race condition where 2 threads are trying to log and one gets the lock with
117 | //the other waiting and the lead thread sets the delegate, logs all cached events and then returns, allowing
118 | //the blocked thread to acquire the lock. At that time, the delegate is no longer null and the event is logged
119 | //directly to it, rather than being cached.
120 | if (delegate == null) {
121 | synchronized (lock) {
122 | //Note the isStarted() check here. If multiple threads are logging at the time the ApplicationContext
123 | //becomes available, the first thread to acquire the lock _may_ stop this appender if the context does
124 | //not contain an Appender with the expected name. If that happens, when the lock is released and other
125 | //threads acquire it, isStarted() will return false and those threads should return without trying to
126 | //use either the delegate or the cache--both of which will be null.
127 | if (!isStarted()) {
128 | return;
129 | }
130 | //If we're still started either no thread has attempted to load the delegate yet, or the delegate has
131 | //been loaded successfully. If the latter, the delegate will no longer be null
132 | if (delegate == null) {
133 | if (ApplicationContextHolder.hasApplicationContext()) {
134 | //First, load the delegate Appender from the ApplicationContext. If it cannot be loaded, this
135 | //appender will be stopped and null will be returned.
136 | Appender appender = getDelegate();
137 | if (appender == null) {
138 | return;
139 | }
140 |
141 | //Once we have the appender, unload the cache to it.
142 | List cachedEvents = cache.get();
143 | for (ILoggingEvent cachedEvent : cachedEvents) {
144 | appender.doAppend(cachedEvent);
145 | }
146 |
147 | //If we've found our delegate appender, we no longer need the cache.
148 | cache = null;
149 | delegate = appender;
150 | } else {
151 | //Otherwise, if the ApplicationContext is not ready yet, cache this event and wait
152 | cache.put(event);
153 |
154 | return;
155 | }
156 | }
157 | }
158 | }
159 |
160 | //If we make it here, the delegate should always be non-null and safe to append to.
161 | delegate.doAppend(event);
162 | }
163 |
164 | private Appender getDelegate() {
165 | ApplicationContext context = ApplicationContextHolder.getApplicationContext();
166 |
167 | try {
168 | @SuppressWarnings("unchecked")
169 | Appender appender = context.getBean(beanName, Appender.class);
170 | appender.setContext(getContext());
171 | if (!appender.isStarted()) {
172 | appender.start();
173 | }
174 | return appender;
175 | } catch (NoSuchBeanDefinitionException e) {
176 | stop();
177 | addError("The ApplicationContext does not contain an Appender named [" + beanName +
178 | "]. This delegating appender will now stop processing events.", e);
179 | }
180 | return null;
181 | }
182 |
183 | public String getBeanName() {
184 | return beanName;
185 | }
186 |
187 | public void setBeanName(String beanName) {
188 | this.beanName = beanName;
189 | }
190 | }
191 |
192 |
--------------------------------------------------------------------------------
/spring/src/main/java/ch/qos/logback/ext/spring/EventCacheMode.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.spring;
17 |
18 | import ch.qos.logback.classic.spi.ILoggingEvent;
19 |
20 | import java.lang.ref.SoftReference;
21 | import java.util.ArrayList;
22 | import java.util.Collections;
23 | import java.util.List;
24 |
25 | /**
26 | * @author Bryan Turner
27 | * @since 0.1
28 | */
29 | public enum EventCacheMode {
30 |
31 | OFF {
32 | @Override
33 | public ILoggingEventCache createCache() {
34 | return new ILoggingEventCache() {
35 |
36 | @Override
37 | public List get() {
38 | return Collections.emptyList();
39 | }
40 |
41 | @Override
42 | public void put(ILoggingEvent event) {
43 | //When caching is off, events are discarded as they are received
44 | }
45 | };
46 | }
47 | },
48 | ON {
49 | @Override
50 | public ILoggingEventCache createCache() {
51 | return new ILoggingEventCache() {
52 |
53 | private List events = new ArrayList();
54 |
55 | @Override
56 | public List get() {
57 | List list = Collections.unmodifiableList(events);
58 | events = null;
59 | return list;
60 | }
61 |
62 | @Override
63 | public void put(ILoggingEvent event) {
64 | events.add(event);
65 | }
66 | };
67 | }
68 | },
69 | SOFT {
70 | @Override
71 | public ILoggingEventCache createCache() {
72 | return new ILoggingEventCache() {
73 |
74 | private List> references = new ArrayList>();
75 |
76 | @Override
77 | public List get() {
78 | List events = new ArrayList(references.size());
79 | for (SoftReference reference : references) {
80 | ILoggingEvent event = reference.get();
81 | if (event != null) {
82 | events.add(event);
83 | }
84 | }
85 | references = null;
86 | return Collections.unmodifiableList(events);
87 | }
88 |
89 | @Override
90 | public void put(ILoggingEvent event) {
91 | references.add(new SoftReference(event));
92 | }
93 | };
94 | }
95 | };
96 |
97 | public abstract ILoggingEventCache createCache();
98 | }
99 |
--------------------------------------------------------------------------------
/spring/src/main/java/ch/qos/logback/ext/spring/ILoggingEventCache.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.spring;
17 |
18 | import ch.qos.logback.classic.spi.ILoggingEvent;
19 |
20 | import java.util.List;
21 |
22 | /**
23 | * Abstraction interface for defining a cache for Logback {@code ILoggingEvent} instances.
24 | *
25 | * @author Bryan Turner
26 | * @since 0.1
27 | */
28 | public interface ILoggingEventCache {
29 |
30 | /**
31 | * Retrieves a list containing 0 or more cached {@code ILoggingEvent}s.
32 | *
33 | * Note: Implementations of this method must return a non-{@code null} list, even if the list is empty, and the
34 | * returned list must not contain any {@code null} elements. If the caching implementation has discarded any of
35 | * the events that were passed to {@link #put(ILoggingEvent)}, they should be completely omitted from the event
36 | * list returned.
37 | *
38 | * @return a non-{@code null} list containing 0 or more cached events
39 | */
40 | List get();
41 |
42 | /**
43 | * Stores the provided event in the cache.
44 | *
45 | * Note: Implementations are free to "store" the event in a destructive or potentially-destructive way. This means
46 | * the "cache" may actually just discard any events it receives, or it may wrap them in a {@code SoftReference} or
47 | * other {@code java.lang.ref} type which could potentially result in the event being garbage collected before the
48 | * {@link #get()} method is called.
49 | *
50 | * @param event the event to cache
51 | */
52 | void put(ILoggingEvent event);
53 | }
54 |
--------------------------------------------------------------------------------
/spring/src/main/java/ch/qos/logback/ext/spring/LogbackConfigurer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.spring;
17 |
18 | import java.io.File;
19 | import java.io.FileNotFoundException;
20 | import java.net.URL;
21 |
22 | import org.slf4j.impl.StaticLoggerBinder;
23 | import org.springframework.util.ResourceUtils;
24 | import org.springframework.util.SystemPropertyUtils;
25 |
26 | import ch.qos.logback.classic.LoggerContext;
27 | import ch.qos.logback.classic.selector.ContextSelector;
28 | import ch.qos.logback.classic.util.ContextInitializer;
29 | import ch.qos.logback.classic.util.ContextSelectorStaticBinder;
30 | import ch.qos.logback.core.joran.spi.JoranException;
31 |
32 | /**
33 | * Convenience class that features simple methods for custom Log4J configuration.
34 | *
35 | * Only needed for non-default Logback initialization with a custom
36 | * config location. By default, Logback will simply read its
37 | * configuration from a "logback.xml" or "logback_test.xml" file in the root of the classpath.
38 | *
39 | * For web environments, the analogous LogbackWebConfigurer class can be found
40 | * in the web package, reading in its configuration from context-params in web.xml.
41 | * In a JEE web application, Logback is usually set up via LogbackConfigListener or
42 | * LogbackConfigServlet, delegating to LogbackWebConfigurer underneath.
43 | *
44 | * @author Juergen Hoeller
45 | * @author Bryan Turner
46 | * @author Les Hazlewood
47 | * @author Knute Axelson
48 | * @see ch.qos.logback.ext.spring.web.WebLogbackConfigurer WebLogbackConfigurer
49 | * @see ch.qos.logback.ext.spring.web.LogbackConfigListener LogbackConfigListener
50 | * @see ch.qos.logback.ext.spring.web.LogbackConfigServlet LogbackConfigServlet
51 | * @since 0.1
52 | */
53 | public class LogbackConfigurer {
54 |
55 | private LogbackConfigurer() {
56 | }
57 |
58 | /**
59 | * Initialize logback from the given file.
60 | *
61 | * @param location the location of the config file: either a "classpath:" location
62 | * (e.g. "classpath:logback.xml"), an absolute file URL
63 | * (e.g. "file:C:/logback.xml), or a plain absolute path in the file system
64 | * (e.g. "C:/logback.xml")
65 | * @throws java.io.FileNotFoundException if the location specifies an invalid file path
66 | * @throws ch.qos.logback.core.joran.spi.JoranException
67 | * Thrown
68 | */
69 | public static void initLogging(String location) throws FileNotFoundException, JoranException {
70 | String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
71 | URL url = ResourceUtils.getURL(resolvedLocation);
72 | LoggerContext loggerContext = (LoggerContext)StaticLoggerBinder.getSingleton().getLoggerFactory();
73 |
74 | // in the current version logback automatically configures at startup the context, so we have to reset it
75 | loggerContext.reset();
76 |
77 | // reinitialize the logger context. calling this method allows configuration through groovy or xml
78 | new ContextInitializer(loggerContext).configureByResource(url);
79 | }
80 |
81 | /**
82 | * Set the specified system property to the current working directory.
83 | *
84 | * This can be used e.g. for test environments, for applications that leverage
85 | * LogbackWebConfigurer's "webAppRootKey" support in a web environment.
86 | *
87 | * @param key system property key to use, as expected in Logback configuration
88 | * (for example: "demo.root", used as "${demo.root}/WEB-INF/demo.log")
89 | * @see ch.qos.logback.ext.spring.web.WebLogbackConfigurer WebLogbackConfigurer
90 | */
91 | public static void setWorkingDirSystemProperty(String key) {
92 | System.setProperty(key, new File("").getAbsolutePath());
93 | }
94 |
95 | /**
96 | * Shut down Logback.
97 | *
98 | * This isn't strictly necessary, but recommended for shutting down
99 | * logback in a scenario where the host VM stays alive (for example, when
100 | * shutting down an application in a J2EE environment).
101 | */
102 | public static void shutdownLogging() {
103 | ContextSelector selector = ContextSelectorStaticBinder.getSingleton().getContextSelector();
104 | LoggerContext loggerContext = selector.getLoggerContext();
105 | String loggerContextName = loggerContext.getName();
106 | LoggerContext context = selector.detachLoggerContext(loggerContextName);
107 | context.reset();
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/spring/src/main/java/ch/qos/logback/ext/spring/web/LogbackConfigListener.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.spring.web;
17 |
18 | import javax.servlet.ServletContextEvent;
19 | import javax.servlet.ServletContextListener;
20 |
21 | /**
22 | * Bootstrap listener for custom Logback initialization in a web environment.
23 | * Delegates to WebLogbackConfigurer (see its javadoc for configuration details).
24 | *
25 | * WARNING: Assumes an expanded WAR file, both for loading the configuration
26 | * file and for writing the log files. If you want to keep your WAR unexpanded or
27 | * don't need application-specific log files within the WAR directory, don't use
28 | * Logback setup within the application (thus, don't use Log4jConfigListener or
29 | * LogbackConfigServlet). Instead, use a global, VM-wide Log4J setup (for example,
30 | * in JBoss) or JDK 1.4's java.util.logging
(which is global too).
31 | *
32 | * This listener should be registered before ContextLoaderListener in web.xml,
33 | * when using custom Logback initialization.
34 | *
35 | * For Servlet 2.2 containers and Servlet 2.3 ones that do not initialize listeners before servlets, use
36 | * LogbackConfigServlet. See the ContextLoaderServlet javadoc for details.
37 | *
38 | * @author Juergen Hoeller
39 | * @author Les Hazlewood
40 | * @see WebLogbackConfigurer
41 | * @see LogbackConfigListener
42 | * @see LogbackConfigServlet
43 | * @since 0.1
44 | */
45 | public class LogbackConfigListener implements ServletContextListener {
46 |
47 | @Override
48 | public void contextDestroyed(ServletContextEvent event) {
49 | WebLogbackConfigurer.shutdownLogging(event.getServletContext());
50 | }
51 |
52 | @Override
53 | public void contextInitialized(ServletContextEvent event) {
54 | WebLogbackConfigurer.initLogging(event.getServletContext());
55 | }
56 | }
--------------------------------------------------------------------------------
/spring/src/main/java/ch/qos/logback/ext/spring/web/LogbackConfigServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.spring.web;
17 |
18 | import javax.servlet.http.HttpServlet;
19 | import javax.servlet.http.HttpServletRequest;
20 | import javax.servlet.http.HttpServletResponse;
21 | import java.io.IOException;
22 |
23 | /**
24 | * Bootstrap servlet for custom Logback initialization in a web environment.
25 | * Delegates to LogbackWebConfigurer (see its javadoc for configuration details).
26 | *
27 | * WARNING: Assumes an expanded WAR file, both for loading the configuration
28 | * file and for writing the log files. If you want to keep your WAR unexpanded or
29 | * don't need application-specific log files within the WAR directory, don't use
30 | * Logback setup within the application (thus, don't use LogbackConfigListener or
31 | * LogbackConfigServlet). Instead, use a global, VM-wide Logback setup (for example,
32 | * in JBoss) or JDK 1.4's java.util.logging
(which is global too).
33 | *
34 | * Note: This servlet should have a lower load-on-startup
value
35 | * in web.xml
than ContextLoaderServlet, when using custom Logback
36 | * initialization.
37 | *
38 | * Note that this class has been deprecated for containers implementing
39 | * Servlet API 2.4 or higher, in favor of LogbackConfigListener.
40 | * According to Servlet 2.4, listeners must be initialized before load-on-startup
41 | * servlets. Many Servlet 2.3 containers already enforce this behavior
42 | * (see ContextLoaderServlet javadocs for details). If you use such a container,
43 | * this servlet can be replaced with LogbackConfigListener. Else or if working
44 | * with a Servlet 2.2 container, stick with this servlet.
45 | *
46 | * @author Juergen Hoeller
47 | * @author Les Hazlewood
48 | * @see WebLogbackConfigurer
49 | * @see LogbackConfigListener
50 | * @since 0.1
51 | */
52 | public class LogbackConfigServlet extends HttpServlet {
53 |
54 | @Override
55 | public void init() {
56 | WebLogbackConfigurer.initLogging(getServletContext());
57 | }
58 |
59 | @Override
60 | public void destroy() {
61 | WebLogbackConfigurer.shutdownLogging(getServletContext());
62 | }
63 |
64 | @Override
65 | public String getServletInfo() {
66 | return "LogbackConfigServlet for Servlet API 2.2/2.3 " +
67 | "(deprecated in favor of LogbackConfigListener for Servlet API 2.4+)";
68 | }
69 |
70 | /**
71 | * This should never even be called since no mapping to this servlet should
72 | * ever be created in web.xml. That's why a correctly invoked Servlet 2.3
73 | * listener is much more appropriate for initialization work ;-)
74 | */
75 | @Override
76 | public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
77 | getServletContext().log(
78 | "Attempt to call service method on LogbackConfigServlet as [" +
79 | request.getRequestURI() + "] was ignored");
80 | response.sendError(HttpServletResponse.SC_BAD_REQUEST);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/spring/src/main/java/ch/qos/logback/ext/spring/web/WebLogbackConfigurer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 The logback-extensions developers (logback-user@qos.ch)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package ch.qos.logback.ext.spring.web;
17 |
18 | import ch.qos.logback.core.joran.spi.JoranException;
19 | import ch.qos.logback.ext.spring.LogbackConfigurer;
20 |
21 | import org.springframework.context.ConfigurableApplicationContext;
22 | import org.springframework.util.ClassUtils;
23 | import org.springframework.util.ReflectionUtils;
24 | import org.springframework.util.ResourceUtils;
25 | import org.springframework.util.StringUtils;
26 | import org.springframework.web.util.ServletContextPropertyUtils;
27 | import org.springframework.web.util.WebUtils;
28 |
29 | import javax.servlet.ServletContext;
30 | import java.io.FileNotFoundException;
31 | import java.lang.reflect.Method;
32 |
33 | /**
34 | * Convenience class that performs custom Logback initialization for web environments,
35 | * allowing for log file paths within the web application.
36 | *
37 | * WARNING: Assumes an expanded WAR file, both for loading the configuration
38 | * file and for writing the log files. If you want to keep your WAR unexpanded or
39 | * don't need application-specific log files within the WAR directory, don't use
40 | * Logback setup within the application (thus, don't use LogbackConfigListener or
41 | * LogbackConfigServlet). Instead, use a global, VM-wide Logback setup (for example,
42 | * in JBoss) or JDK 1.4's java.util.logging
(which is global too).
43 | *
44 | * Supports two init parameters at the servlet context level (that is,
45 | * context-param entries in web.xml):
46 | *
47 | * - "logbackConfigLocation":
48 | * Location of the Logback config file; either a "classpath:" location (e.g.
49 | * "classpath:myLogback.xml"), an absolute file URL (e.g. "file:C:/logback.properties),
50 | * or a plain path relative to the web application root directory (e.g.
51 | * "/WEB-INF/logback.xml"). If not specified, default Logback initialization will
52 | * apply ("logback.xml" or "logback_test.xml" in the class path; see Logback documentation for details).
53 | * - "logbackExposeWebAppRoot":
54 | * Whether the web app root system property should be exposed, allowing for log
55 | * file paths relative to the web application root directory. Default is "true";
56 | * specify "false" to suppress expose of the web app root system property. See
57 | * below for details on how to use this system property in log file locations.
58 | *
59 | *
60 | * Note: initLogging
should be called before any other Spring activity
61 | * (when using Logback), for proper initialization before any Spring logging attempts.
62 | *
63 | * By default, this configurer automatically sets the web app root system property,
64 | * for "${key}" substitutions within log file locations in the Logback config file,
65 | * allowing for log file paths relative to the web application root directory.
66 | * The default system property key is "webapp.root", to be used in a Logback config
67 | * file like as follows:
68 | *
69 | *
70 | *
71 | *
72 | * %-4relative [%thread] %-5level %class - %msg%n
73 | *
74 | * ${webapp.root}/WEB-INF/demo.log
75 | *
76 | *
77 | *
78 | * Alternatively, specify a unique context-param "webAppRootKey" per web application.
79 | * For example, with "webAppRootKey = "demo.root":
80 | *
81 | *
82 | *
83 | *
84 | * %-4relative [%thread] %-5level %class - %msg%n
85 | *
86 | * ${demo.root}/WEB-INF/demo.log
87 | *
88 | *
89 | *
90 | * WARNING: Some containers (like Tomcat) do not keep system properties
91 | * separate per web app. You have to use unique "webAppRootKey" context-params per web
92 | * app then, to avoid clashes. Other containers like Resin do isolate each web app's
93 | * system properties: Here you can use the default key (i.e. no "webAppRootKey"
94 | * context-param at all) without worrying.
95 | *
96 | * @author Juergen Hoeller
97 | * @author Les Hazlewood
98 | * @see org.springframework.util.Log4jConfigurer
99 | * @see org.springframework.web.util.Log4jConfigListener
100 | * @since 0.1
101 | */
102 | public class WebLogbackConfigurer {
103 |
104 | /**
105 | * Parameter specifying the location of the logback config file
106 | */
107 | public static final String CONFIG_LOCATION_PARAM = "logbackConfigLocation";
108 | /**
109 | * Parameter specifying whether to expose the web app root system property
110 | */
111 | public static final String EXPOSE_WEB_APP_ROOT_PARAM = "logbackExposeWebAppRoot";
112 |
113 | private WebLogbackConfigurer() {
114 | }
115 |
116 | /**
117 | * Initialize Logback, including setting the web app root system property.
118 | *
119 | * @param servletContext the current ServletContext
120 | * @see org.springframework.web.util.WebUtils#setWebAppRootSystemProperty
121 | */
122 | public static void initLogging(ServletContext servletContext) {
123 | // Expose the web app root system property.
124 | if (exposeWebAppRoot(servletContext)) {
125 | WebUtils.setWebAppRootSystemProperty(servletContext);
126 | }
127 |
128 | // Only perform custom Logback initialization in case of a config file.
129 | String locationParam = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
130 | if (locationParam != null) {
131 | // Perform Logback initialization; else rely on Logback's default initialization.
132 | for (String location : StringUtils.tokenizeToStringArray(locationParam,
133 | ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)) {
134 | try {
135 | // Resolve context property placeholders before potentially resolving real path.
136 | location = ServletContextPropertyUtils.resolvePlaceholders(location, servletContext);
137 | // Return a URL (e.g. "classpath:" or "file:") as-is;
138 | // consider a plain file path as relative to the web application root directory.
139 | if (!ResourceUtils.isUrl(location)) {
140 | location = WebUtils.getRealPath(servletContext, location);
141 | }
142 |
143 | // Write log message to server log.
144 | servletContext.log("Initializing Logback from [" + location + "]");
145 |
146 | // Initialize
147 | LogbackConfigurer.initLogging(location);
148 | break;
149 | } catch (FileNotFoundException ex) {
150 | servletContext.log("No logback configuration file found at [" + location + "]");
151 | //throw new IllegalArgumentException("Invalid 'logbackConfigLocation' parameter: " + ex.getMessage());
152 | } catch (JoranException e) {
153 | throw new RuntimeException("Unexpected error while configuring logback", e);
154 | }
155 | }
156 | }
157 |
158 | //If SLF4J's java.util.logging bridge is available in the classpath, install it. This will direct any messages
159 | //from the Java Logging framework into SLF4J. When logging is terminated, the bridge will need to be uninstalled
160 | try {
161 | Class> julBridge = ClassUtils.forName("org.slf4j.bridge.SLF4JBridgeHandler", ClassUtils.getDefaultClassLoader());
162 |
163 | Method removeHandlers = ReflectionUtils.findMethod(julBridge, "removeHandlersForRootLogger");
164 | if (removeHandlers != null) {
165 | servletContext.log("Removing all previous handlers for JUL to SLF4J bridge");
166 | ReflectionUtils.invokeMethod(removeHandlers, null);
167 | }
168 |
169 | Method install = ReflectionUtils.findMethod(julBridge, "install");
170 | if (install != null) {
171 | servletContext.log("Installing JUL to SLF4J bridge");
172 | ReflectionUtils.invokeMethod(install, null);
173 | }
174 | } catch (ClassNotFoundException ignored) {
175 | //Indicates the java.util.logging bridge is not in the classpath. This is not an indication of a problem.
176 | servletContext.log("JUL to SLF4J bridge is not available on the classpath");
177 | }
178 | }
179 |
180 | /**
181 | * Shut down Logback, properly releasing all file locks
182 | * and resetting the web app root system property.
183 | *
184 | * @param servletContext the current ServletContext
185 | * @see WebUtils#removeWebAppRootSystemProperty
186 | */
187 | public static void shutdownLogging(ServletContext servletContext) {
188 | //Uninstall the SLF4J java.util.logging bridge *before* shutting down the Logback framework.
189 | try {
190 | Class> julBridge = ClassUtils.forName("org.slf4j.bridge.SLF4JBridgeHandler", ClassUtils.getDefaultClassLoader());
191 | Method uninstall = ReflectionUtils.findMethod(julBridge, "uninstall");
192 | if (uninstall != null) {
193 | servletContext.log("Uninstalling JUL to SLF4J bridge");
194 | ReflectionUtils.invokeMethod(uninstall, null);
195 | }
196 | } catch (ClassNotFoundException ignored) {
197 | //No need to shutdown the java.util.logging bridge. If it's not on the classpath, it wasn't started either.
198 | }
199 |
200 | try {
201 | servletContext.log("Shutting down Logback");
202 | LogbackConfigurer.shutdownLogging();
203 | } finally {
204 | // Remove the web app root system property.
205 | if (exposeWebAppRoot(servletContext)) {
206 | WebUtils.removeWebAppRootSystemProperty(servletContext);
207 | }
208 | }
209 | }
210 |
211 | /**
212 | * Return whether to expose the web app root system property,
213 | * checking the corresponding ServletContext init parameter.
214 | *
215 | * @param servletContext the servlet context
216 | * @return {@code true} if the webapp's root should be exposed; otherwise, {@code false}
217 | * @see #EXPOSE_WEB_APP_ROOT_PARAM
218 | */
219 | @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion"})
220 | private static boolean exposeWebAppRoot(ServletContext servletContext) {
221 | String exposeWebAppRootParam = servletContext.getInitParameter(EXPOSE_WEB_APP_ROOT_PARAM);
222 | return (exposeWebAppRootParam == null || Boolean.valueOf(exposeWebAppRootParam));
223 | }
224 | }
225 |
--------------------------------------------------------------------------------