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 com.groupon.vertx.utils;
17 |
18 | /**
19 | * Log wrapper that includes common fields that we want in all log messages.
20 | *
21 | * @author Stuart Siegrist (fsiegrist at groupon dot com)
22 | * @author Gil Markham (gil at groupon dot com)
23 | * @since 1.0.0
24 | */
25 | public interface Logger {
26 |
27 | /**
28 | * Returns a new logger for the specified target class and event source. If the event source is not specified it
29 | * will default to the simple name of the target class.
30 | *
31 | * @param targetClass target class for logging
32 | * @param eventSource the eventSource
33 | * @return a new com.groupon.vertx.utils.Logger
34 | */
35 | static Logger getLogger(Class> targetClass, String eventSource) {
36 | return new LoggerImpl(targetClass, eventSource);
37 | }
38 |
39 | /**
40 | * Returns a new logger for the specified target class. The logger's event source is defaulted to the
41 | * simple name of the target class with the first letter lower cased.
42 | *
43 | * @param targetClass target class for logging
44 | * @return a new com.groupon.vertx.utils.Logger
45 | */
46 | static Logger getLogger(Class> targetClass) {
47 | return new LoggerImpl(targetClass, null);
48 | }
49 |
50 | void error(String method, String event, String reason);
51 |
52 | void error(String method, String event, String reason, Throwable throwable);
53 |
54 | void error(String method, String event, String reason, String[] extraValueNames, Object... extraValues);
55 |
56 | void info(String method, String event);
57 |
58 | void info(String method, String event, String[] extraValueNames, Object... extraValues);
59 |
60 | void warn(String method, String event);
61 |
62 | void warn(String method, String event, Throwable throwable);
63 |
64 | void warn(String method, String event, String[] extraValueNames, Object... extraValues);
65 |
66 | void debug(String method, String event);
67 |
68 | void debug(String method, String event, String[] extraValueNames, Object... extraValues);
69 |
70 | void trace(String method, String event);
71 |
72 | void trace(String method, String event, String[] extraValueNames, Object... extraValues);
73 |
74 | boolean isInfoEnabled();
75 |
76 | boolean isWarnEnabled();
77 |
78 | boolean isTraceEnabled();
79 |
80 | boolean isDebugEnabled();
81 |
82 | boolean isErrorEnabled();
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/groupon/vertx/utils/LoggerImpl.java:
--------------------------------------------------------------------------------
1 | package com.groupon.vertx.utils;
2 |
3 | import java.util.Locale;
4 |
5 | import com.arpnetworking.logback.StenoMarker;
6 | import org.slf4j.LoggerFactory;
7 |
8 | class LoggerImpl implements Logger {
9 | private static final String[] EMPTY_EXTRA_NAMES = new String[0];
10 | private org.slf4j.Logger slf4jLog;
11 | private String eventSource;
12 | private static final String[] BASE_KEYS = new String[]{"eventSource", "method"};
13 | private static final int BASE_KEYS_LENGTH = BASE_KEYS.length;
14 | private static final String[] BASE_ERROR_KEYS = new String[]{"eventSource", "method", "reason"};
15 | private static final int BASE_ERROR_KEYS_LENGTH = BASE_ERROR_KEYS.length;
16 | private static final int EVENT_SOURCE_INDEX = 0;
17 | private static final int METHOD_NAME_INDEX = 1;
18 | private static final int REASON_INDEX = 2;
19 |
20 | LoggerImpl(Class> targetClass, String eventSource) {
21 | this.slf4jLog = LoggerFactory.getLogger(targetClass);
22 |
23 | if (eventSource == null) {
24 | String simpleName = targetClass.getSimpleName();
25 | this.eventSource = simpleName.substring(0, 1).toLowerCase(Locale.getDefault()) + simpleName.substring(1);
26 | } else {
27 | this.eventSource = eventSource;
28 | }
29 | }
30 |
31 | public void error(String method, String event, String reason) {
32 | error(method, event, reason, null);
33 | }
34 |
35 | public void error(String method, String event, String reason, Throwable throwable) {
36 | error(method, event, reason, EMPTY_EXTRA_NAMES, throwable);
37 | }
38 |
39 | public void error(String method, String event, String reason, String[] extraValueNames, Object... extraValues) {
40 | if (isErrorEnabled()) {
41 | String[] errorKeyArray = buildErrorKeyArray(extraValueNames);
42 | Object[] errorValueArray = buildErrorValueArray(method, reason, extraValues, errorKeyArray.length);
43 | Throwable error = extractThrowable(extraValueNames, extraValues);
44 |
45 | if (error != null) {
46 | slf4jLog.error(StenoMarker.ARRAY_MARKER, event, errorKeyArray, errorValueArray, error);
47 | } else {
48 | slf4jLog.error(StenoMarker.ARRAY_MARKER, event, errorKeyArray, errorValueArray);
49 | }
50 | }
51 | }
52 |
53 | public void info(String method, String event) {
54 | info(method, event, null);
55 | }
56 |
57 | public void info(String method, String event, String[] extraValueNames, Object... extraValues) {
58 | if (isInfoEnabled()) {
59 | String[] keyArray = buildKeyArray(extraValueNames);
60 | Object[] valueArray = buildValueArray(method, extraValues, keyArray.length);
61 | Throwable error = extractThrowable(extraValueNames, extraValues);
62 |
63 | if (error != null) {
64 | slf4jLog.info(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray, error);
65 | } else {
66 | slf4jLog.info(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray);
67 | }
68 | }
69 | }
70 |
71 | public void warn(String method, String event) {
72 | warn(method, event, null);
73 | }
74 |
75 | public void warn(String method, String event, Throwable throwable) {
76 | warn(method, event, EMPTY_EXTRA_NAMES, throwable);
77 | }
78 |
79 | public void warn(String method, String event, String[] extraValueNames, Object... extraValues) {
80 | if (isWarnEnabled()) {
81 | String[] keyArray = buildKeyArray(extraValueNames);
82 | Object[] valueArray = buildValueArray(method, extraValues, keyArray.length);
83 | Throwable error = extractThrowable(extraValueNames, extraValues);
84 |
85 | if (error != null) {
86 | slf4jLog.warn(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray, error);
87 | } else {
88 | slf4jLog.warn(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray);
89 | }
90 | }
91 | }
92 |
93 | public void debug(String method, String event) {
94 | debug(method, event, null);
95 | }
96 |
97 | public void debug(String method, String event, String[] extraValueNames, Object... extraValues) {
98 | if (isDebugEnabled()) {
99 | String[] keyArray = buildKeyArray(extraValueNames);
100 | Object[] valueArray = buildValueArray(method, extraValues, keyArray.length);
101 | Throwable error = extractThrowable(extraValueNames, extraValues);
102 |
103 | if (error != null) {
104 | slf4jLog.debug(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray, error);
105 | } else {
106 | slf4jLog.debug(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray);
107 | }
108 | }
109 | }
110 |
111 | public void trace(String method, String event) {
112 | trace(method, event, null);
113 | }
114 |
115 | public void trace(String method, String event, String[] extraValueNames, Object... extraValues) {
116 | if (isTraceEnabled()) {
117 | String[] keyArray = buildKeyArray(extraValueNames);
118 | Object[] valueArray = buildValueArray(method, extraValues, keyArray.length);
119 | Throwable error = extractThrowable(extraValueNames, extraValues);
120 |
121 | if (error != null) {
122 | slf4jLog.trace(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray, error);
123 | } else {
124 | slf4jLog.trace(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray);
125 | }
126 | }
127 | }
128 |
129 | private Throwable extractThrowable(String[] keys, Object[] values) {
130 | Throwable error = null;
131 |
132 | int keyLength = keys == null ? 0 : keys.length;
133 | if (values != null && values.length > keyLength) {
134 | if (values[values.length - 1] instanceof Throwable) {
135 | error = (Throwable) values[values.length - 1];
136 | }
137 | }
138 |
139 | return error;
140 | }
141 |
142 | private String[] buildErrorKeyArray(String[] keys) {
143 | if (keys == null || keys.length == 0) {
144 | return BASE_ERROR_KEYS;
145 | } else {
146 | String[] newKeyArray = new String[keys.length + BASE_ERROR_KEYS.length];
147 | int i = 0;
148 | for (; i < BASE_ERROR_KEYS.length; i++) {
149 | newKeyArray[i] = BASE_ERROR_KEYS[i];
150 | }
151 |
152 | for (; i - BASE_ERROR_KEYS.length < keys.length; i++) {
153 | newKeyArray[i] = keys[i - BASE_ERROR_KEYS.length];
154 | }
155 |
156 | return newKeyArray;
157 | }
158 | }
159 |
160 | private String[] buildKeyArray(String[] keys) {
161 | if (keys == null || keys.length == 0) {
162 | return BASE_KEYS;
163 | } else {
164 | String[] newKeyArray = new String[keys.length + BASE_KEYS.length];
165 | int i = 0;
166 | for (; i < BASE_KEYS.length; i++) {
167 | newKeyArray[i] = BASE_KEYS[i];
168 | }
169 |
170 | for (; i - BASE_KEYS.length < keys.length; i++) {
171 | newKeyArray[i] = keys[i - BASE_KEYS.length];
172 | }
173 |
174 | return newKeyArray;
175 | }
176 | }
177 |
178 | private Object[] buildErrorValueArray(String method, String reason, Object[] values, int keyLength) {
179 | Object[] newValues;
180 | if (values == null || values.length == 0) {
181 | newValues = new Object[BASE_ERROR_KEYS_LENGTH];
182 | newValues[EVENT_SOURCE_INDEX] = eventSource;
183 | newValues[METHOD_NAME_INDEX] = method;
184 | newValues[REASON_INDEX] = reason;
185 | } else {
186 | // Length should always be the base error keys plus the number of named value keys.
187 | int valueLength = Math.min(values.length + BASE_ERROR_KEYS_LENGTH, keyLength);
188 | newValues = new Object[valueLength];
189 | newValues[EVENT_SOURCE_INDEX] = eventSource;
190 | newValues[METHOD_NAME_INDEX] = method;
191 | newValues[REASON_INDEX] = reason;
192 | for (int i = 0; i < valueLength - BASE_ERROR_KEYS_LENGTH; i++) {
193 | newValues[i + BASE_ERROR_KEYS_LENGTH] = values[i];
194 | }
195 | }
196 | return newValues;
197 | }
198 |
199 | private Object[] buildValueArray(String method, Object[] values, int keyLength) {
200 | Object[] newValues;
201 | if (values == null || values.length == 0) {
202 | newValues = new Object[BASE_KEYS_LENGTH];
203 | newValues[EVENT_SOURCE_INDEX] = eventSource;
204 | newValues[METHOD_NAME_INDEX] = method;
205 | } else {
206 | // Length should always be the base keys plus the number of named value key pairs.
207 | int valueLength = Math.min(values.length + BASE_KEYS_LENGTH, keyLength);
208 | newValues = new Object[valueLength];
209 | newValues[EVENT_SOURCE_INDEX] = eventSource;
210 | newValues[METHOD_NAME_INDEX] = method;
211 | for (int i = 0; i < valueLength - BASE_KEYS_LENGTH; i++) {
212 | newValues[i + BASE_KEYS_LENGTH] = values[i];
213 | }
214 | }
215 | return newValues;
216 | }
217 |
218 | public boolean isInfoEnabled() {
219 | return slf4jLog.isInfoEnabled();
220 | }
221 |
222 | public boolean isWarnEnabled() {
223 | return slf4jLog.isWarnEnabled();
224 | }
225 |
226 | public boolean isTraceEnabled() {
227 | return slf4jLog.isTraceEnabled();
228 | }
229 |
230 | public boolean isDebugEnabled() {
231 | return slf4jLog.isDebugEnabled();
232 | }
233 |
234 | public boolean isErrorEnabled() {
235 | return slf4jLog.isErrorEnabled();
236 | }
237 |
238 | void setSlf4jLog(org.slf4j.Logger slf4jLog) { // for testing
239 | this.slf4jLog = slf4jLog;
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/main/java/com/groupon/vertx/utils/MainVerticle.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015 Groupon.com
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 com.groupon.vertx.utils;
17 |
18 | import java.lang.reflect.InvocationTargetException;
19 |
20 | import io.vertx.core.AbstractVerticle;
21 | import io.vertx.core.Future;
22 | import io.vertx.core.Promise;
23 | import io.vertx.core.Vertx;
24 | import io.vertx.core.eventbus.MessageCodec;
25 | import io.vertx.core.json.JsonArray;
26 | import io.vertx.core.json.JsonObject;
27 |
28 | import com.groupon.vertx.utils.config.ConfigLoader;
29 | import com.groupon.vertx.utils.deployment.DeploymentFactory;
30 | import com.groupon.vertx.utils.deployment.MultiVerticleDeployment;
31 |
32 | /**
33 | * Main verticle used to deploy the appropriate number of instances of the different verticles that
34 | * make up the push service. This is done instead of providing the number of instances on the command line
35 | * so we can have a single instance of the metrics reporter and have greater control of the number of instances
36 | * of downstream verticles that we need.
37 | *
38 | * @author Gil Markham (gil at groupon dot com)
39 | * @author Tristan Blease (tblease at groupon dot com)
40 | * @since 1.0.0
41 | * @version 2.0.1
42 | */
43 | public class MainVerticle extends AbstractVerticle {
44 | private static final Logger log = Logger.getLogger(MainVerticle.class, "mainVerticle");
45 | private static final String ABORT_ON_FAILURE_FIELD = "abortOnFailure";
46 | private static final String MESSAGE_CODECS_FIELD = "messageCodecs";
47 |
48 | /**
49 | * @param startedResult future indicating when all verticles have been deployed successfully
50 | */
51 | @Override
52 | public void start(final Promise
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 com.groupon.vertx.utils.config;
17 |
18 | import java.lang.reflect.InvocationTargetException;
19 | import java.util.concurrent.ConcurrentHashMap;
20 | import java.util.concurrent.ConcurrentMap;
21 |
22 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
23 | import io.vertx.core.AsyncResult;
24 | import io.vertx.core.Future;
25 | import io.vertx.core.Handler;
26 | import io.vertx.core.Promise;
27 | import io.vertx.core.file.FileSystem;
28 | import io.vertx.core.json.JsonObject;
29 |
30 | /**
31 | * Asynchronous config loading for verticles
32 | *
33 | * @author Tristan Blease (tblease at groupon dot com)
34 | * @since 2.0.1
35 | * @version 2.0.1
36 | */
37 | public class ConfigLoader {
38 | private final ConcurrentMap
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 com.groupon.vertx.utils;
17 |
18 | import static org.mockito.ArgumentMatchers.eq;
19 | import static org.mockito.Mockito.doThrow;
20 | import static org.mockito.Mockito.times;
21 | import static org.mockito.Mockito.verify;
22 | import static org.mockito.Mockito.when;
23 |
24 | import io.netty.handler.codec.http.HttpHeaderNames;
25 | import io.netty.handler.codec.http.HttpResponseStatus;
26 | import io.vertx.core.AsyncResult;
27 | import io.vertx.core.Handler;
28 | import io.vertx.core.Vertx;
29 | import io.vertx.core.file.FileSystem;
30 | import io.vertx.core.http.HttpMethod;
31 | import io.vertx.core.http.HttpServerRequest;
32 | import io.vertx.core.http.HttpServerResponse;
33 | import org.junit.jupiter.api.BeforeEach;
34 | import org.junit.jupiter.api.Test;
35 | import org.mockito.ArgumentCaptor;
36 | import org.mockito.Captor;
37 | import org.mockito.Mock;
38 | import org.mockito.MockitoAnnotations;
39 |
40 | /**
41 | * @author Stuart Siegrist (fsiegrist at groupon dot com)
42 | * @since 1.0.0
43 | */
44 | public class HealthcheckHandlerTest {
45 | private static final HttpResponseStatus OK = HttpResponseStatus.OK;
46 | private static final HttpResponseStatus SERVICE_UNAVAILABLE = HttpResponseStatus.SERVICE_UNAVAILABLE;
47 | private static final String CONTENT_TYPE = "text/plain";
48 | private static final String CACHE_CONTROL = "private, no-cache, no-store, must-revalidate";
49 |
50 | @Mock
51 | private Vertx vertx;
52 |
53 | @Mock
54 | private FileSystem fileSystem;
55 |
56 | @Mock
57 | private HttpServerRequest request;
58 |
59 | @Mock
60 | private HttpServerResponse response;
61 |
62 | @Mock
63 | private AsyncResult
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 com.groupon.vertx.utils;
17 |
18 | import static org.junit.jupiter.api.Assertions.assertArrayEquals;
19 | import static org.junit.jupiter.api.Assertions.assertEquals;
20 | import static org.junit.jupiter.api.Assertions.assertNotNull;
21 | import static org.mockito.ArgumentMatchers.eq;
22 | import static org.mockito.Mockito.times;
23 | import static org.mockito.Mockito.verify;
24 | import static org.mockito.Mockito.when;
25 |
26 | import java.time.Clock;
27 | import java.time.Instant;
28 | import java.time.ZoneId;
29 | import java.util.List;
30 |
31 | import com.arpnetworking.logback.StenoMarker;
32 | import org.junit.jupiter.api.BeforeEach;
33 | import org.junit.jupiter.api.Test;
34 | import org.junit.jupiter.api.extension.ExtendWith;
35 | import org.mockito.ArgumentCaptor;
36 | import org.mockito.Captor;
37 | import org.mockito.Mock;
38 | import org.mockito.junit.jupiter.MockitoExtension;
39 |
40 | /**
41 | * @author Gil Markham (gil at groupon dot com)
42 | */
43 | @ExtendWith(MockitoExtension.class)
44 | public class LoggerTest {
45 | @Mock
46 | private org.slf4j.Logger slf4jLogger;
47 | private Logger logger;
48 | @Captor
49 | private ArgumentCaptor