├── .gitignore
├── src
├── main
│ └── java
│ │ └── io
│ │ └── github
│ │ └── dibog
│ │ ├── LoggingEventToString.java
│ │ ├── AwsCredentials.java
│ │ ├── LoggingEventToStringImpl.java
│ │ ├── AwsConfig.java
│ │ ├── ExceptionUtil.java
│ │ ├── SkippedEvent.java
│ │ ├── AwsLogAppender.java
│ │ ├── RingBuffer.java
│ │ └── AwsCWEventDump.java
└── test
│ ├── java
│ └── io
│ │ └── github
│ │ └── dibog
│ │ └── Main.java
│ └── resources
│ └── logback.xml
├── pom.xml
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | target/
3 | *.iml
4 |
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/LoggingEventToString.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import ch.qos.logback.classic.spi.ILoggingEvent;
20 |
21 | interface LoggingEventToString {
22 | String map(ILoggingEvent event);
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/AwsCredentials.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import com.amazonaws.auth.AWSCredentials;
20 |
21 | public class AwsCredentials implements AWSCredentials{
22 | private String accessKeyId;
23 | private String secretAccessKey;
24 |
25 | public String getAccessKeyId() {
26 | return accessKeyId;
27 | }
28 |
29 | public void setAccessKeyId(String accessKeyId) {
30 | this.accessKeyId = accessKeyId;
31 | }
32 |
33 | public void setSecretAccessKey(String secretAccessKey) {
34 | this.secretAccessKey = secretAccessKey;
35 | }
36 |
37 | @Override
38 | public String getAWSAccessKeyId() {
39 | return accessKeyId;
40 | }
41 |
42 | @Override
43 | public String getAWSSecretKey() {
44 | return secretAccessKey;
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/test/java/io/github/dibog/Main.java:
--------------------------------------------------------------------------------
1 | package io.github.dibog;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.slf4j.MDC;
6 |
7 | import java.sql.SQLException;
8 |
9 | public class Main {
10 |
11 | private static final int QUEUE_LEN = 100;
12 |
13 | public static Exception makeNestedException() {
14 |
15 | SQLException e = new SQLException("Some message");
16 | SQLException e1 = new SQLException("Sub exception #1");
17 | SQLException e2 = new SQLException("Sub exception #2");
18 | SQLException e3 = new SQLException("Sub sub exception");
19 |
20 | e2.addSuppressed(e3);
21 | e.addSuppressed(e1);
22 | e.addSuppressed(e2);
23 |
24 | return new RuntimeException("Main exception", e);
25 | }
26 |
27 | public static void main(String[] args) throws InterruptedException {
28 | testLog();
29 | //testMDC();
30 | }
31 |
32 | public static void testLog() throws InterruptedException {
33 | Logger logger = LoggerFactory.getLogger(Main.class);
34 |
35 | for(int i=0; i<2*QUEUE_LEN; ++i) {
36 | logger.info("Message {}", i);
37 | // Thread.sleep(10_000L );
38 | }
39 |
40 | Thread.sleep(20_000L);
41 |
42 | System.out.println("### Done");
43 | }
44 |
45 | public static void testMDC() throws InterruptedException {
46 | MDC.put("customer_name", "Jon Snow");
47 | Logger logger = LoggerFactory.getLogger(Main.class);
48 | // HOW TO TEST: Set breakpoint at the end of io.github.dibog.LoggingEventToStringImpl.map,
49 | // check that the values written include the MDC key
50 | for (int i=0; i<200; ++i) {
51 | logger.info("This message should be logged together with the given MDC");
52 | }
53 | Thread.sleep(20_000L);
54 |
55 | System.out.println("### Done");
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | mosaic
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 100
23 | foo
24 | log-complex
25 | yyyyMMdd_HHmm
26 |
27 |
28 |
29 |
30 |
31 |
32 | mosaic
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 100
49 | foo
50 | log-simple
51 | yyyyMMdd_HHmm
52 |
53 |
54 | [%X{a} %X{b}] %-4relative [%thread] %-5level %logger{35} - %msg %n
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/LoggingEventToStringImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import ch.qos.logback.classic.spi.ILoggingEvent;
20 | import ch.qos.logback.classic.spi.IThrowableProxy;
21 | import com.fasterxml.jackson.databind.ObjectMapper;
22 | import org.slf4j.Marker;
23 |
24 | import java.util.HashMap;
25 | import java.util.Map;
26 |
27 | class LoggingEventToStringImpl implements LoggingEventToString {
28 | private final ObjectMapper m = new ObjectMapper();
29 |
30 | @Override
31 | public String map(ILoggingEvent event) {
32 |
33 | Map values = new HashMap<>();
34 |
35 | values.put("message", event.getFormattedMessage());
36 | values.put("level", event.getLevel().levelStr);
37 | values.put("logger-name", event.getLoggerName());
38 | values.put("thread-name", event.getThreadName());
39 |
40 | Marker marker = event.getMarker();
41 | if(marker!=null)
42 | values.put("marker", marker);
43 |
44 | IThrowableProxy exceptionProxy = event.getThrowableProxy();
45 | if(exceptionProxy!=null) {
46 | values.put("exception", ExceptionUtil.toString(exceptionProxy));
47 | }
48 |
49 | // In theory, event.getMDCPropertyMap() should not be null, in practice it can
50 | if (event.getMDCPropertyMap() != null && !event.getMDCPropertyMap().isEmpty()) {
51 | values.put("context", event.getMDCPropertyMap());
52 | }
53 |
54 | try {
55 | return m.writeValueAsString(values);
56 | }
57 | catch(Exception e) {
58 | return e.getLocalizedMessage();
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/AwsConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import com.amazonaws.ClientConfiguration;
20 | import com.amazonaws.auth.AWSStaticCredentialsProvider;
21 | import com.amazonaws.auth.profile.ProfileCredentialsProvider;
22 | import com.amazonaws.services.logs.AWSLogs;
23 | import com.amazonaws.services.logs.AWSLogsClientBuilder;
24 |
25 | public class AwsConfig {
26 | private ClientConfiguration clientConfig;
27 | private AwsCredentials credentials;
28 | private String profileName;
29 | private String region;
30 |
31 | public void setCredentials(AwsCredentials credentials) {
32 | this.credentials = credentials;
33 | }
34 |
35 | public void setClientConfig(ClientConfiguration clientConfig) {
36 | this.clientConfig = clientConfig;
37 | }
38 |
39 | public void setRegion(String region) {
40 | this.region = region;
41 | }
42 |
43 | public void setProfileName(String profileName) {
44 | this.profileName = profileName;
45 | }
46 |
47 | public AWSLogs createAWSLogs() {
48 | AWSLogsClientBuilder builder = AWSLogsClientBuilder.standard();
49 |
50 | if(region!=null) {
51 | builder.withRegion(region);
52 | }
53 |
54 | if(clientConfig!=null) {
55 | builder.withClientConfiguration(clientConfig);
56 | }
57 |
58 | if(profileName!=null) {
59 | builder.withCredentials(new ProfileCredentialsProvider(profileName));
60 | }
61 | else if(credentials!=null) {
62 | builder.withCredentials(new AWSStaticCredentialsProvider(credentials));
63 | }
64 |
65 | return builder.build();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/ExceptionUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import ch.qos.logback.classic.spi.IThrowableProxy;
20 | import ch.qos.logback.classic.spi.StackTraceElementProxy;
21 |
22 | class ExceptionUtil {
23 |
24 | public static String toString(IThrowableProxy aException) {
25 | StringBuilder sb = new StringBuilder();
26 | appendProxy(sb, "", "", aException);
27 | return sb.toString();
28 | }
29 |
30 | private static void appendProxy(StringBuilder sb, String aPrefix, String aHeader, IThrowableProxy aException) {
31 |
32 | sb.append(aPrefix + aHeader + aException.getClassName() + ": " +aException.getMessage() + "\n");
33 |
34 | String prefix = aPrefix+"\t";
35 | appendStackTrace(sb, prefix, aException);
36 |
37 | IThrowableProxy[] suppressed = aException.getSuppressed();
38 | if(suppressed!=null) {
39 | for(IThrowableProxy supp : suppressed) {
40 | if(supp!=null) {
41 | appendProxy(sb, prefix, "Suppressed: ", supp);
42 | }
43 | }
44 | }
45 |
46 | IThrowableProxy cause = aException.getCause();
47 | if(cause!=null) {
48 | appendProxy(sb, prefix, "Causd by: ", cause);
49 | }
50 | }
51 |
52 | private static void appendStackTrace(StringBuilder sb, String prefix, IThrowableProxy aProxy) {
53 |
54 | StackTraceElementProxy[] frames = aProxy.getStackTraceElementProxyArray();
55 | int commonFrames = aProxy.getCommonFrames();
56 | for(int i=0, size=frames.length-commonFrames; i0) {
60 | sb.append(prefix+"... "+commonFrames+" more"+"\n");
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/SkippedEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import ch.qos.logback.classic.Level;
20 | import ch.qos.logback.classic.spi.ILoggingEvent;
21 | import ch.qos.logback.classic.spi.IThrowableProxy;
22 | import ch.qos.logback.classic.spi.LoggerContextVO;
23 | import org.slf4j.Marker;
24 |
25 | import java.util.Map;
26 |
27 | class SkippedEvent implements ILoggingEvent {
28 | private final int no;
29 | private final LoggerContextVO loggerContextVO;
30 |
31 | public SkippedEvent(int aMessages, LoggerContextVO aLoggerContextV0) {
32 | no = aMessages;
33 | loggerContextVO = aLoggerContextV0;
34 | }
35 |
36 | @Override
37 | public String getThreadName() {
38 | return Thread.currentThread().getName();
39 | }
40 |
41 | @Override
42 | public Level getLevel() {
43 | return Level.WARN;
44 | }
45 |
46 | @Override
47 | public String getMessage() {
48 | return "Skipped "+no+" messages in the last log cycle. (See in https://github.com/dibog/cloudwatch-logback-appender/)";
49 | }
50 |
51 | @Override
52 | public Object[] getArgumentArray() {
53 | return null;
54 | }
55 |
56 | @Override
57 | public String getFormattedMessage() {
58 | return getMessage();
59 | }
60 |
61 | @Override
62 | public String getLoggerName() {
63 | return "AWS Cloud Watch Logger";
64 | }
65 |
66 | @Override
67 | public LoggerContextVO getLoggerContextVO() {
68 | return loggerContextVO;
69 | }
70 |
71 | @Override
72 | public IThrowableProxy getThrowableProxy() {
73 | return null;
74 | }
75 |
76 | @Override
77 | public StackTraceElement[] getCallerData() {
78 | return null;
79 | }
80 |
81 | @Override
82 | public boolean hasCallerData() {
83 | return false;
84 | }
85 |
86 | @Override
87 | public Marker getMarker() {
88 | return null;
89 | }
90 |
91 | @Override
92 | public Map getMDCPropertyMap() {
93 | return null;
94 | }
95 |
96 | @Override
97 | public Map getMdc() {
98 | return null;
99 | }
100 |
101 | @Override
102 | public long getTimeStamp() {
103 | return System.currentTimeMillis();
104 | }
105 |
106 | @Override
107 | public void prepareForDeferredProcessing() { }
108 | }
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/AwsLogAppender.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import ch.qos.logback.classic.spi.ILoggingEvent;
20 | import ch.qos.logback.core.AppenderBase;
21 | import ch.qos.logback.core.Layout;
22 |
23 | public class AwsLogAppender extends AppenderBase {
24 |
25 | private AwsCWEventDump dump;
26 |
27 | AwsConfig awsConfig;
28 | String groupName;
29 | boolean createLogGroup = true;
30 | String streamName;
31 | String dateFormat;
32 | int queueLength = 500;
33 | Layout layout;
34 |
35 | public void setAwsConfig(AwsConfig config) {
36 | this.awsConfig = config;
37 | addInfo("AwsConfig was set to "+config);
38 | }
39 |
40 | public void setLayout(Layout layout) {
41 | this.layout = layout;
42 | }
43 |
44 | public void setGroupName(String groupName) {
45 | addInfo("groupName was set to "+groupName);
46 | this.groupName = groupName;
47 | }
48 |
49 | public void setStreamName(String streamName) {
50 | addInfo("streamName was set to "+streamName);
51 | this.streamName = streamName;
52 | }
53 |
54 | public void setDateFormat(String dateFormat) {
55 | addInfo("dateFormat was set to "+dateFormat);
56 | this.dateFormat = dateFormat;
57 | }
58 |
59 | public void setQueueLength(int aLength) {
60 | addInfo("queueLength was set to "+aLength);
61 | queueLength = aLength;
62 | }
63 |
64 | public void setCreateLogGroup(boolean createLogGroup) {
65 | addInfo("createLogGroup was set to "+createLogGroup);
66 | this.createLogGroup = createLogGroup;
67 | }
68 |
69 | @Override
70 | protected void append(ILoggingEvent event) {
71 |
72 | AwsCWEventDump queue = dump;
73 | if (dump != null) {
74 | event.prepareForDeferredProcessing();
75 | dump.queue(event);
76 | }
77 |
78 | }
79 |
80 | @Override
81 | public void start() {
82 | dump = new AwsCWEventDump(this );
83 |
84 | Thread t = new Thread(dump);
85 | t.setDaemon(true);
86 | t.start();
87 |
88 | super.start();
89 | }
90 |
91 | @Override
92 | public void stop() {
93 | super.stop();
94 | if(dump!=null) {
95 | // flush it
96 | dump.shutdown();
97 | }
98 | dump = null;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/RingBuffer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import java.util.Collection;
20 | import java.util.concurrent.atomic.AtomicInteger;
21 | import java.util.concurrent.locks.Condition;
22 | import java.util.concurrent.locks.Lock;
23 | import java.util.concurrent.locks.ReentrantLock;
24 |
25 | class RingBuffer {
26 | public final Lock lock = new ReentrantLock();
27 | private final Condition empty = lock.newCondition();
28 | private final AtomicInteger skipped = new AtomicInteger(0);
29 | private final int[] result = new int[2];
30 |
31 | private final E[] buffer;
32 | private final int cap;
33 | private int head = 0;
34 | private int size = 0;
35 |
36 | public RingBuffer(int aCapacity) {
37 | if(aCapacity<=0) throw new IllegalArgumentException("Capacity must be positive");
38 | cap = aCapacity;
39 | buffer = (E[])new Object[aCapacity];
40 | }
41 |
42 | /** Adds an item to the ring buffer.
43 | *
44 | * @param aElement the item to be added
45 | *
46 | * @return true if an item was overwritten
47 | * @return false if the item could be inserted without removing an old one
48 | *
49 | */
50 | public boolean put(E aElement) {
51 | lock.lock();
52 |
53 | try {
54 | E prev = buffer[head];
55 | buffer[head] = aElement;
56 |
57 | head++;
58 | if(head>=cap) {
59 | head -= cap;
60 | }
61 |
62 | size++;
63 | if(size>cap) {
64 | size = cap;
65 | }
66 |
67 | empty.signalAll();
68 |
69 | boolean overwritten = prev!=null;
70 | if(overwritten) {
71 | skipped.incrementAndGet();
72 | }
73 | return overwritten;
74 | }
75 | finally {
76 | lock.unlock();
77 | }
78 | }
79 |
80 | private int inc(int current) {
81 | assert 0<=current;
82 |
83 | int next = current+1;
84 | if(next>=cap) {
85 | next = next % cap;
86 | }
87 |
88 | return next;
89 | }
90 |
91 | /** Moves all items of the queue into the collection (FIFO).
92 | *
93 | * @param aCollection the collection into which the items are moved.
94 | *
95 | * @return (nbOfMessageCollected, nbOfSkippedMessages)
96 | */
97 | public int[] drainTo(Collection aCollection) throws InterruptedException {
98 | lock.lock();
99 | try {
100 |
101 | while(size<=0) {
102 | empty.await();
103 | }
104 |
105 | final int base = head-size;
106 | for(int i=0; i=cap) {
113 | index -= cap;
114 | }
115 |
116 | E elem = buffer[index];
117 | assert elem!=null;
118 |
119 | buffer[index]=null;
120 |
121 | aCollection.add(elem);
122 | }
123 |
124 | result[0] = size;
125 | result[1] = skipped.getAndSet(0);
126 | size = 0;
127 |
128 | return result;
129 | }
130 | finally {
131 | lock.unlock();
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | io.github.dibog
22 | cloudwatch-logback-appender
23 | 1.0.7-SNAPSHOT
24 |
25 | cloudwatch-logback-appender
26 | Logback Appender which uses stores the log entries in ASW Cloudwatch logs.
27 |
28 |
29 | Apache License, Version 2.0
30 | https://www.apache.org/licenses/LICENSE-2.0
31 |
32 |
33 |
34 | https://github.com/dibog/cloudwatch-logback-appender
35 |
36 |
37 |
38 | dibog
39 | Dieter Bogdoll
40 | dibog@github.com
41 |
42 |
43 | architect
44 | developer
45 |
46 | +1
47 |
48 |
49 |
50 |
51 | 1.7
52 | 1.7
53 | 1.11.292
54 | 1.2.3
55 |
56 |
57 |
58 |
59 |
60 | com.amazonaws
61 | aws-java-sdk-bom
62 | ${aws.version}
63 | pom
64 | import
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | ch.qos.logback
73 | logback-classic
74 | ${logback.version}
75 |
76 |
77 |
78 | com.amazonaws
79 | aws-java-sdk-logs
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | maven-deploy-plugin
89 | 2.8.2
90 |
91 |
92 | default-deploy
93 | deploy
94 |
95 | deploy
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | org.apache.maven.plugins
104 | maven-release-plugin
105 | 2.5.3
106 |
107 |
108 | true
109 | false
110 | forked-path
111 | -Dgpg.passphrase=${gpg.passphrase}
112 |
113 |
114 |
115 |
116 | org.apache.maven.scm
117 | maven-scm-provider-gitexe
118 | 1.9.5
119 |
120 |
121 |
122 |
123 |
124 |
125 | org.sonatype.plugins
126 | nexus-staging-maven-plugin
127 | 1.6.7
128 | true
129 |
130 |
131 | ossrh
132 | https://oss.sonatype.org/
133 | true
134 |
135 |
136 |
137 |
138 |
139 |
140 | org.apache.maven.plugins
141 | maven-source-plugin
142 | 3.0.1
143 |
144 |
145 |
146 | attach-sources
147 |
148 | jar
149 |
150 |
151 |
152 | attach-javadoc
153 |
154 | jar
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | ossrh
168 | https://oss.sonatype.org/content/repositories/snapshots
169 |
170 |
171 |
172 | ossrh
173 | https://oss.sonatype.org/service/local/staging/deploy/maven2
174 |
175 |
176 |
177 |
178 | scm:git:git://github.com/dibog/cloudwatch-logback-appender.git
179 | scm:git:git@github.com:dibog/cloudwatch-logback-appender.git
180 | https://github.com/dibog/cloudwatch-logback
181 | HEAD
182 |
183 |
184 |
185 |
186 |
187 |
188 | release-sign-artifacts
189 |
190 |
191 | performRelease
192 | true
193 |
194 |
195 |
196 |
197 |
198 |
199 | org.apache.maven.plugins
200 | maven-gpg-plugin
201 | 1.6
202 |
203 |
204 | sign-artifacts
205 | verify
206 |
207 | sign
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Cloudwatch Logback Appender
2 | ===========================
3 |
4 | ## License
5 |
6 | This project is licensed under the [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.html).
7 |
8 | The copyright owner is Dieter Bogdoll.
9 |
10 | ## Overview
11 | This project provides a [logback appender](https://logback.qos.ch/) whichs target is [AWS Cloudwatch Logs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html).
12 |
13 | ## Maven
14 |
15 |
16 | io.github.dibog
17 | cloudwatch-logback-appender
18 | 1.0.6
19 |
20 |
21 | ## Configuration
22 | With the following XML fragment you can configure your Cloudtwatch logback appender:
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | awsProfile
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | false
43 | 100
44 | group-name
45 | stream-name
46 | yyyyMMdd_HHmm
47 |
48 |
49 | [%X{a} %X{b}] %-4relative [%thread] %-5level %logger{35} - %msg %n
50 |
51 |
52 |
53 |
54 | To be able to use the appender the IAM profile under which the logging is running requires
55 | at least the following AWS permissions:
56 | * ``logs:DescribeLogStreams``
57 | * ``logs:PutLogEvents``
58 |
59 | The section ```` is optional and on an EC2 instance. It usually is not required as long
60 | as you have attached an IAM profile to your instance with the right permissions and/or have
61 | set the environment variables required to provide the AWS credentials.
62 |
63 | But if that section is available in the configuration it will be used instead of the data
64 | from the environment.
65 |
66 | To authenticate you can have currently three mechanism:
67 | * Use the tag ```` to specify the name of profile.
68 | * The use of tag ```` is self explanatory if you know your AWS. Just determine your values for this
69 | section and enter them here.
70 | * Don't specify anything and you should get the IAM settings of your EC2 instance.
71 |
72 |
73 | The sub section ```` should contain the AWS region into which the log information
74 | should be streamed, please find here the [actual list of regions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#available_regions).
75 | You have to use the format you can find in the left column, e.g. like ``eu-central-1``.
76 |
77 | The ```` is again used mainly when your logging process is not run on an EC2 instance,
78 | but somewhere outside of AWS. Please lookup the [ClientConfiguration](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/ClientConfiguration.html) within the AWS documentation.
79 |
80 | And here now the remaining configuration elements:
81 |
82 | * ````: Valid arguments: ``true`` or ``false``, where ``true`` requires the IAM User to have
83 | the permissions ``logs:CreateLogGroup`` and ``logs:DescribeLogGroups``. Setting ``true`` allows the logger appender
84 | to create the required logGroup in AWS. Setting it to ``false`` means that the logger appender expect
85 | that the logGroup already exists. The default value is ``false``.
86 |
87 | * ````: Valid Arguments: an positive integer indicating the queue length which is used
88 | to decouple the calling of the logger appender and the writing of the logging events into AWS.
89 | The argument is optional and has an default value. If the queue is not long enough you will get a message
90 | like ``Skipped messages in the last log cycle.`` within the log. Enlarging the queue length
91 | might resolve this issue when there are some bursts of log message from time to time.
92 |
93 | * ````: The name of the log group. If ```` was configured to ``true`` the log group
94 | will be created it it does not yet exist.
95 |
96 | * ````: The name of the log stream. If the argument ```` is set, then the
97 | content of ```` is used as prefix otherwise it is taken as specified. The log stream will
98 | be created on the fly.
99 |
100 | * ````: If a valid [SimpleDateFormat string](https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html) is
101 | provided it will be used with the ```` to create a combined log stream. This feature
102 | can be used to create multiple log streams of the live time of the logging process. Everytime the
103 | SimpleDateFormat will yield a new stream name the current log stream will be closed and a new
104 | one created.
105 |
106 | * ````: If exist it will be used to transform the logging event to a string which is stored in cloudwatch logs.
107 | ( See https://logback.qos.ch/manual/layouts.html#PatternLayout. )
108 | If the tag is missing, the logging event will be transformed into a json object.
109 |
110 | ## Tips and Tricks
111 |
112 | ### [](#unique-log-stream-names) Unique log stream names
113 |
114 | To make the log stream name unqiue across the same application and multiple ec2 instances,
115 | we can use the variable substitution mechanism of logback:
116 |
117 |
118 |
119 |
120 | stream-name-${instance.id}
121 |
122 |
123 |
124 | And set the variable (in our case) `instance.id` via either `-D` from the command line, or via calling
125 | `System.setProperty("instance.id", uniqueId)` as one of the first methods in your main.
126 |
127 | Setting via `-D` is the recommended way.
128 |
129 | ## Caveats
130 |
131 | The [pom.xml](pom.xml) used for this project binds to a specific version of the AWS SDK.
132 | In case you are using in your project also the AWS SDK the changes are high that the
133 | version is different to that ot the cloudwatch-logback-appender, and this could lead to problems.
134 | If the version of your AWS SDK is smaller then the one of cloudwatch-logback-appender I advise
135 | you to upgrade to the latest [AWS SDK version](https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk).
136 | If your AWS SDK is version is later then the one used by cloudwatch-logback-appender you could
137 | replace the dependecy to cloudwatch-logback-appender like this:
138 |
139 |
140 | com.amazonaws
141 | aws-java-sdk-logs
142 | VERSION_OF_OUR_AWS_SDK
143 |
144 |
145 |
146 | io.github.dibog
147 | cloudwatch-logback-appender
148 | VERSION_OF_CLOUDWATCH_LOGBACK_APPENDER
149 |
150 |
151 | com.amazonaws
152 | aws-java-sdk-logs
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/src/main/java/io/github/dibog/AwsCWEventDump.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dieter Bogdoll
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 |
17 | package io.github.dibog;
18 |
19 | import ch.qos.logback.classic.spi.ILoggingEvent;
20 | import ch.qos.logback.classic.spi.LoggerContextVO;
21 | import ch.qos.logback.core.Layout;
22 | import ch.qos.logback.core.spi.ContextAware;
23 | import com.amazonaws.services.logs.AWSLogs;
24 | import com.amazonaws.services.logs.model.*;
25 |
26 | import java.text.DateFormat;
27 | import java.text.SimpleDateFormat;
28 | import java.util.*;
29 |
30 | import static java.util.Objects.requireNonNull;
31 |
32 | class AwsCWEventDump implements Runnable {
33 | private final RingBuffer queue;
34 | private final LoggingEventToString layout;
35 | private final AwsConfig awsConfig;
36 | private final boolean createLogGroup;
37 | private final String groupName;
38 | private final String streamName;
39 | private final DateFormat dateFormat;
40 | private final ContextAware logContext;
41 | private final Date dateHolder = new Date();
42 | private final PutLogEventsRequest logEventReq;
43 |
44 | private volatile boolean done = false;
45 |
46 | private AWSLogs awsLogs;
47 | private String currentStreamName = null;
48 | private String nextToken = null;
49 |
50 | public AwsCWEventDump( AwsLogAppender aAppender ) {
51 | logContext = requireNonNull(aAppender, "appender");
52 | awsConfig = aAppender.awsConfig==null ? new AwsConfig(): aAppender.awsConfig;
53 | queue = new RingBuffer(aAppender.queueLength);
54 | createLogGroup = aAppender.createLogGroup;
55 | groupName = requireNonNull(aAppender.groupName, "appender.groupName");
56 | logEventReq = new PutLogEventsRequest().withLogGroupName(groupName);
57 | streamName = requireNonNull(aAppender.streamName, "appender.streamName");
58 |
59 | if(aAppender.layout==null) {
60 | layout = new LoggingEventToStringImpl();
61 | }
62 | else {
63 | final Layout delegate = aAppender.layout;
64 | layout = new LoggingEventToString() {
65 | @Override
66 | public String map(ILoggingEvent event) {
67 | return delegate.doLayout(event);
68 | }
69 | };
70 | }
71 |
72 | if(aAppender.dateFormat==null || aAppender.dateFormat.trim().isEmpty()) {
73 | dateFormat = null;
74 | }
75 | else {
76 | dateFormat = new SimpleDateFormat(aAppender.dateFormat);
77 | }
78 | }
79 |
80 | private void closeStream() {
81 | currentStreamName = null;
82 | }
83 |
84 | private void openStream(String aNewStreamName) {
85 |
86 | if(awsLogs==null) {
87 | try {
88 | awsLogs = awsConfig.createAWSLogs();
89 | }
90 | catch(Exception e) {
91 | logContext.addError("Exception while opening AWSLogs. Shutting down the cloud watch logger.", e);
92 | shutdown();
93 | }
94 | }
95 |
96 | if(createLogGroup) {
97 | if (findLogGroup(groupName)==null) {
98 | logContext.addInfo("creating log group '"+groupName+"'");
99 | try {
100 | awsLogs.createLogGroup(new CreateLogGroupRequest(groupName));
101 | }
102 | catch(OperationAbortedException e) {
103 | logContext.addError("couldn't create log group '"+groupName+"': "+e.getLocalizedMessage());
104 | }
105 | }
106 | }
107 |
108 | LogStream stream = findLogStream(groupName, aNewStreamName);
109 | if(stream==null) {
110 | try {
111 | logContext.addInfo("creating log stream '"+streamName+"'");
112 | awsLogs.createLogStream(new CreateLogStreamRequest(groupName, aNewStreamName));
113 | }
114 | catch(Exception e) {
115 | logContext.addError("Exception while creating log stream ( "+groupName+" / "+aNewStreamName+" ). Shutting down the cloud watch logger.", e);
116 | shutdown();
117 | }
118 | nextToken = null;
119 | }
120 | else {
121 | nextToken = stream.getUploadSequenceToken();
122 | }
123 |
124 | logEventReq.withLogStreamName(aNewStreamName);
125 | currentStreamName = aNewStreamName;
126 |
127 | }
128 |
129 | private LogGroup findLogGroup(String aName) {
130 | DescribeLogGroupsResult result = awsLogs.describeLogGroups(
131 | new DescribeLogGroupsRequest()
132 | .withLogGroupNamePrefix(groupName)
133 | );
134 | for (LogGroup group : result.getLogGroups()) {
135 | if (group.getLogGroupName().equals(aName)) {
136 | return group;
137 | }
138 | }
139 | return null;
140 | }
141 |
142 | private LogStream findLogStream(String aGroupName, String aStreamName) {
143 | try {
144 | DescribeLogStreamsResult result = awsLogs.describeLogStreams(
145 | new DescribeLogStreamsRequest(groupName)
146 | .withLogStreamNamePrefix(aStreamName)
147 | );
148 | for (LogStream stream : result.getLogStreams()) {
149 | if (stream.getLogStreamName().equals(aStreamName)) {
150 | return stream;
151 | }
152 | }
153 | }
154 | catch(Exception e) {
155 | logContext.addError("Exception while trying to describe log stream ( "+aGroupName+"/"+aStreamName+" ). Shutting down the cloud watch logger.", e);
156 | shutdown();
157 | }
158 |
159 | return null;
160 | }
161 |
162 | private void log(Collection aEvents) {
163 | if(dateFormat!=null) {
164 | dateHolder.setTime(System.currentTimeMillis());
165 | String newStreamName = streamName + "-" + dateFormat.format(dateHolder);
166 |
167 | if ( !newStreamName.equals(currentStreamName) ) {
168 | logContext.addInfo("stream name changed from '"+currentStreamName+"' to '"+newStreamName+"'");
169 | closeStream();
170 | openStream(newStreamName);
171 | }
172 | }
173 | else if (awsLogs==null) {
174 | closeStream();
175 | openStream(streamName);
176 | }
177 |
178 | Collection events = new ArrayList<>(aEvents.size());
179 | for(ILoggingEvent event : aEvents) {
180 | if(event.getLoggerContextVO()!=null) {
181 | events.add(new InputLogEvent()
182 | .withTimestamp(event.getTimeStamp())
183 | .withMessage(layout.map(event)));
184 | }
185 | }
186 |
187 | try {
188 | nextToken = awsLogs.putLogEvents(
189 | logEventReq
190 | .withSequenceToken(nextToken)
191 | .withLogEvents(events)
192 | ).getNextSequenceToken();
193 | }
194 | catch(Exception e) {
195 | logContext.addError("Exception while adding log events.", e);
196 | }
197 | }
198 |
199 | public void shutdown() {
200 | done = true;
201 | }
202 |
203 | public void queue(ILoggingEvent event) {
204 | queue.put(event);
205 | }
206 |
207 | public void run() {
208 | List collections = new LinkedList();
209 | LoggerContextVO context = null;
210 | while(!done) {
211 |
212 | try {
213 | int[] nbs = queue.drainTo(collections);
214 | if(context==null && !collections.isEmpty()) {
215 | context = collections.get(0).getLoggerContextVO();
216 | }
217 |
218 | int msgProcessed = nbs[0];
219 | int msgSkipped = nbs[1];
220 | if(context!=null && msgSkipped>0) {
221 | collections.add(new SkippedEvent(msgSkipped, context));
222 | }
223 | log(collections);
224 | collections.clear();
225 | }
226 | catch(InterruptedException e) {
227 | // ignoring
228 | }
229 | }
230 | }
231 | }
232 |
233 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------