| @@ -0,0 +1,99 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Deque; | ||
| import java.util.List; | ||
| import java.util.concurrent.ConcurrentLinkedDeque; | ||
| import java.util.concurrent.Executor; | ||
| import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| class AsyncEventLogger extends AbstractEventLogger implements EventLogger, Runnable { | ||
|
|
||
| //0 = not running | ||
| //1 = queued | ||
| //2 = running | ||
| @SuppressWarnings({"unused", "FieldMayBeFinal"}) | ||
| private volatile int state = 0; | ||
|
|
||
| private static final AtomicIntegerFieldUpdater<AsyncEventLogger> stateUpdater = AtomicIntegerFieldUpdater.newUpdater(AsyncEventLogger.class, "state"); | ||
|
|
||
| private final EventWriter writer; | ||
| private final Executor executor; | ||
| private final Deque<Event> pendingMessages; | ||
|
|
||
| AsyncEventLogger(final String id, final EventWriter writer, final Executor executor) { | ||
| super(id); | ||
| this.writer = writer; | ||
| this.executor = executor; | ||
| pendingMessages = new ConcurrentLinkedDeque<>(); | ||
| } | ||
|
|
||
| @Override | ||
| void log(final Event event) { | ||
| pendingMessages.add(event); | ||
| int state = stateUpdater.get(this); | ||
| if (state == 0) { | ||
| if (stateUpdater.compareAndSet(this, 0, 1)) { | ||
| executor.execute(this); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void run() { | ||
| if (!stateUpdater.compareAndSet(this, 1, 2)) { | ||
| return; | ||
| } | ||
| List<Event> events = new ArrayList<>(); | ||
| Event event; | ||
| // Only grab at most 1000 messages at a time | ||
| for (int i = 0; i < 1000; ++i) { | ||
| event = pendingMessages.poll(); | ||
| if (event == null) { | ||
| break; | ||
| } | ||
| events.add(event); | ||
| } | ||
| try { | ||
| if (!events.isEmpty()) { | ||
| writeMessage(events); | ||
| } | ||
| } finally { | ||
| stateUpdater.set(this, 0); | ||
| // Check to see if there is still more messages and run again if there are | ||
| if (!events.isEmpty()) { | ||
| if (stateUpdater.compareAndSet(this, 0, 1)) { | ||
| executor.execute(this); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void writeMessage(final List<Event> events) { | ||
| for (Event event : events) { | ||
| writer.write(event); | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,53 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.time.Instant; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Describes an event that has taken place. | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| public interface Event { | ||
|
|
||
| /** | ||
| * The source of this event. | ||
| * | ||
| * @return the source of this event | ||
| */ | ||
| String getSource(); | ||
|
|
||
| /** | ||
| * The date this event was created. | ||
| * | ||
| * @return the date the event was created | ||
| */ | ||
| @SuppressWarnings("unused") | ||
| Instant getInstant(); | ||
|
|
||
| /** | ||
| * The data associated with this event. | ||
| * | ||
| * @return the data for this event | ||
| */ | ||
| Map<String, Object> getData(); | ||
| } |
| @@ -0,0 +1,37 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| /** | ||
| * A formatter for formatting events. | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| public interface EventFormatter { | ||
|
|
||
| /** | ||
| * Formats the event into a string. | ||
| * | ||
| * @param event the event to format | ||
| * | ||
| * @return the formatted string | ||
| */ | ||
| String format(Event event); | ||
| } |
| @@ -0,0 +1,117 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.concurrent.Executor; | ||
| import java.util.function.Supplier; | ||
|
|
||
| /** | ||
| * A logger for various events such as access or audit logging. | ||
| * <p> | ||
| * Note that a {@linkplain #getEventSource() event source} is an arbitrary string used to differentiate logging events. | ||
| * For example a web access event may have an even source of {@code web-access}. | ||
| * </p> | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| @SuppressWarnings({"StaticMethodOnlyUsedInOneClass", "unused", "UnusedReturnValue"}) | ||
| public interface EventLogger { | ||
|
|
||
| /** | ||
| * Creates a new logger which defaults to writing {@linkplain JsonEventFormatter JSON} to | ||
| * {@link StdoutEventWriter stdout}. | ||
| * | ||
| * @param eventSource the identifier for the source of the event this logger is used for | ||
| * | ||
| * @return a new event logger | ||
| */ | ||
| static EventLogger createLogger(final String eventSource) { | ||
| return new StandardEventLogger(eventSource, StdoutEventWriter.of(JsonEventFormatter.builder().build())); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new event logger. | ||
| * | ||
| * @param eventSource the identifier for the source of the event this logger is used for | ||
| * @param writer the writer this logger will write to | ||
| * | ||
| * @return a new event logger | ||
| */ | ||
| static EventLogger createLogger(final String eventSource, final EventWriter writer) { | ||
| return new StandardEventLogger(eventSource, writer); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new asynchronous logger which defaults to writing {@linkplain JsonEventFormatter JSON} to | ||
| * {@link StdoutEventWriter stdout}. | ||
| * | ||
| * @param eventSource the identifier for the source of the event this logger is used for | ||
| * @param executor the executor to execute the threads in | ||
| * | ||
| * @return the new event logger | ||
| */ | ||
| static EventLogger createAsyncLogger(final String eventSource, final Executor executor) { | ||
| return new AsyncEventLogger(eventSource, StdoutEventWriter.of(JsonEventFormatter.builder().build()), executor); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new asynchronous event logger. | ||
| * | ||
| * @param eventSource the identifier for the source of the event this logger is used for | ||
| * @param writer the writer this logger will write to | ||
| * @param executor the executor to execute the threads in | ||
| * | ||
| * @return a new event logger | ||
| */ | ||
| static EventLogger createAsyncLogger(final String eventSource, final EventWriter writer, final Executor executor) { | ||
| return new AsyncEventLogger(eventSource, writer, executor); | ||
| } | ||
|
|
||
| /** | ||
| * Logs the event. | ||
| * | ||
| * @param event the event to log | ||
| * | ||
| * @return this logger | ||
| */ | ||
| EventLogger log(Map<String, Object> event); | ||
|
|
||
| /** | ||
| * Logs the event. | ||
| * <p> | ||
| * The supplier can lazily load the data. Note that in the cases of an | ||
| * {@linkplain #createAsyncLogger(String, Executor) asynchronous logger} the {@linkplain Supplier#get() data} will | ||
| * be retrieved in a different thread. | ||
| * </p> | ||
| * | ||
| * @param event the event to log | ||
| * | ||
| * @return this logger | ||
| */ | ||
| EventLogger log(Supplier<Map<String, Object>> event); | ||
|
|
||
| /** | ||
| * Returns the source of event this logger is logging. | ||
| * | ||
| * @return the event source | ||
| */ | ||
| String getEventSource(); | ||
| } |
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| /** | ||
| * A writer used to write events. | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| public interface EventWriter extends AutoCloseable { | ||
|
|
||
| /** | ||
| * Writes the event. | ||
| * | ||
| * @param event the event to write | ||
| */ | ||
| void write(Event event); | ||
| } |
| @@ -0,0 +1,248 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.math.BigInteger; | ||
| import java.time.ZoneId; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
| import javax.json.Json; | ||
| import javax.json.JsonArrayBuilder; | ||
| import javax.json.JsonBuilderFactory; | ||
| import javax.json.JsonObjectBuilder; | ||
| import javax.json.JsonValue; | ||
|
|
||
| /** | ||
| * A formatter which transforms the event into a JSON string. | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| public class JsonEventFormatter implements EventFormatter { | ||
|
|
||
| private final JsonBuilderFactory factory; | ||
| private final Map<String, Object> metaData; | ||
| private final String timestampKey; | ||
| private final DateTimeFormatter formatter; | ||
| private final boolean includeTimestamp; | ||
|
|
||
| private JsonEventFormatter(final Map<String, Object> metaData, final String timestampKey, | ||
| final DateTimeFormatter formatter, final boolean includeTimestamp) { | ||
| this.metaData = metaData; | ||
| this.timestampKey = timestampKey; | ||
| this.formatter = formatter; | ||
| this.includeTimestamp = includeTimestamp; | ||
| factory = Json.createBuilderFactory(Collections.emptyMap()); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new builder to build a {@link JsonEventFormatter}. | ||
| * | ||
| * @return a new builder | ||
| */ | ||
| @SuppressWarnings("WeakerAccess") | ||
| public static Builder builder() { | ||
| return new Builder(); | ||
| } | ||
|
|
||
| @Override | ||
| public String format(final Event event) { | ||
| final JsonObjectBuilder builder = factory.createObjectBuilder(); | ||
| builder.add("eventSource", event.getSource()); | ||
| if (includeTimestamp) { | ||
| builder.add(timestampKey, formatter.format(event.getInstant())); | ||
| } | ||
| add(builder, metaData); | ||
| add(builder, event.getData()); | ||
| return builder.build().toString(); | ||
| } | ||
|
|
||
| private void add(final JsonObjectBuilder builder, final Map<String, Object> data) { | ||
| for (Map.Entry<String, Object> entry : data.entrySet()) { | ||
| final String key = entry.getKey(); | ||
| final Object value = entry.getValue(); | ||
| if (value == null) { | ||
| builder.addNull(key); | ||
| } else if (value instanceof Boolean) { | ||
| builder.add(key, (Boolean) value); | ||
| } else if (value instanceof Double) { | ||
| builder.add(key, (Double) value); | ||
| } else if (value instanceof Integer) { | ||
| builder.add(key, (Integer) value); | ||
| } else if (value instanceof Long) { | ||
| builder.add(key, (Long) value); | ||
| } else if (value instanceof String) { | ||
| builder.add(key, (String) value); | ||
| } else if (value instanceof BigDecimal) { | ||
| builder.add(key, (BigDecimal) value); | ||
| } else if (value instanceof BigInteger) { | ||
| builder.add(key, (BigInteger) value); | ||
| } else if (value instanceof Collection) { | ||
| builder.add(key, factory.createArrayBuilder((Collection<?>) value)); | ||
| } else if (value instanceof Map) { | ||
| final Map<?, ?> mapValue = (Map<?, ?>) value; | ||
| final JsonObjectBuilder valueBuilder = factory.createObjectBuilder(); | ||
| // Convert the map to a string/object map | ||
| final Map<String, Object> map = new LinkedHashMap<>(); | ||
| for (Map.Entry<?, ?> valueEntry : mapValue.entrySet()) { | ||
| final Object valueKey = valueEntry.getKey(); | ||
| final Object valueValue = valueEntry.getValue(); | ||
| if (valueKey instanceof String) { | ||
| map.put((String) valueKey, valueValue); | ||
| } else { | ||
| map.put(String.valueOf(valueKey), valueValue); | ||
| } | ||
| } | ||
| add(valueBuilder, map); | ||
| builder.add(key, valueBuilder); | ||
| } else if (value instanceof JsonArrayBuilder) { | ||
| builder.add(key, (JsonArrayBuilder) value); | ||
| } else if (value instanceof JsonObjectBuilder) { | ||
| builder.add(key, (JsonObjectBuilder) value); | ||
| } else if (value instanceof JsonValue) { | ||
| builder.add(key, (JsonValue) value); | ||
| } else if (value.getClass().isArray()) { | ||
| // We'll rely on the array builder to convert to the correct object type | ||
| builder.add(key, factory.createArrayBuilder(Arrays.asList((Object[]) value))); | ||
| } else { | ||
| builder.add(key, String.valueOf(value)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Builder used to create the {@link JsonEventFormatter}. | ||
| */ | ||
| @SuppressWarnings({"unused", "WeakerAccess"}) | ||
| public static class Builder { | ||
| private Map<String, Object> metaData; | ||
| private String timestampKey; | ||
| private DateTimeFormatter formatter; | ||
| private ZoneId zoneId; | ||
| private boolean includeTimestamp = true; | ||
|
|
||
| private Builder() { | ||
| metaData = new LinkedHashMap<>(); | ||
| } | ||
|
|
||
| /** | ||
| * Adds meta-data to the final output. | ||
| * | ||
| * @param key the key to add | ||
| * @param value the value for the key | ||
| * | ||
| * @return this builder | ||
| */ | ||
| public Builder addMetaData(final String key, final Object value) { | ||
| if (metaData == null) { | ||
| metaData = new LinkedHashMap<>(); | ||
| } | ||
| metaData.put(key, value); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Adds meta-data to the final output. | ||
| * | ||
| * @param metaData the meta-data to add | ||
| * | ||
| * @return this builder | ||
| */ | ||
| public Builder addMetaData(final Map<String, Object> metaData) { | ||
| if (this.metaData == null) { | ||
| this.metaData = new LinkedHashMap<>(); | ||
| } | ||
| this.metaData.putAll(metaData); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the key for the timestamp for the event. The default is {@code timestamp}. | ||
| * | ||
| * @param timestampKey the key name or {@code null} to revert to the default | ||
| * | ||
| * @return this builder | ||
| */ | ||
| public Builder setTimestampKey(final String timestampKey) { | ||
| this.timestampKey = timestampKey; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Set the formatter used to format the timestamp on the event. The default is | ||
| * {@linkplain DateTimeFormatter#ISO_OFFSET_DATE_TIME ISO-8601}. | ||
| * <p> | ||
| * Note the {@linkplain #setZoneId(ZoneId) zone id} is {@linkplain DateTimeFormatter#withZone(ZoneId) zone id} | ||
| * on the formatter. | ||
| * </p> | ||
| * | ||
| * @param formatter the formatter to use or {@code null} to revert to the default. | ||
| * | ||
| * @return this builder | ||
| */ | ||
| public Builder setTimestampFormatter(final DateTimeFormatter formatter) { | ||
| this.formatter = formatter; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Set the zone id for the timestamp. The default is {@link ZoneId#systemDefault()}. | ||
| * | ||
| * @param zoneId the zone id to use or {@code null} to revert to the default | ||
| * | ||
| * @return this builder | ||
| */ | ||
| public Builder setZoneId(final ZoneId zoneId) { | ||
| this.zoneId = zoneId; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets whether or not the timestamp should be added to the output. The default is {@code true}. If set to | ||
| * {@code false} the {@linkplain #setZoneId(ZoneId) zone id} and | ||
| * {@linkplain #setTimestampFormatter(DateTimeFormatter) format} are ignored. | ||
| * | ||
| * @param includeTimestamp {@code true} to include the timestamp or {@code false} to leave the timestamp off | ||
| * | ||
| * @return this builder | ||
| */ | ||
| public Builder setIncludeTimestamp(final boolean includeTimestamp) { | ||
| this.includeTimestamp = includeTimestamp; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Creates the {@link JsonEventFormatter}. | ||
| * | ||
| * @return the newly created formatter | ||
| */ | ||
| public JsonEventFormatter build() { | ||
| final Map<String, Object> metaData = (this.metaData == null ? Collections.emptyMap() : new LinkedHashMap<>(this.metaData)); | ||
| final String timestampKey = (this.timestampKey == null ? "timestamp" : this.timestampKey); | ||
| final DateTimeFormatter formatter = (this.formatter == null ? DateTimeFormatter.ISO_OFFSET_DATE_TIME : this.formatter); | ||
| final ZoneId zoneId = (this.zoneId == null ? ZoneId.systemDefault() : this.zoneId); | ||
| return new JsonEventFormatter(metaData, timestampKey, formatter.withZone(zoneId), includeTimestamp); | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,43 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
| import java.util.function.Supplier; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| class LazyEvent extends AbstractEvent implements Event { | ||
|
|
||
| private final Supplier<Map<String, Object>> data; | ||
|
|
||
| LazyEvent(final String eventSource, final Supplier<Map<String, Object>> data) { | ||
| super(eventSource); | ||
| this.data = data; | ||
| } | ||
|
|
||
| @Override | ||
| public Map<String, Object> getData() { | ||
| return Collections.unmodifiableMap(new LinkedHashMap<>(data.get())); | ||
| } | ||
| } |
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| class StandardEvent extends AbstractEvent implements Event { | ||
| private final Map<String, Object> data; | ||
|
|
||
| StandardEvent(final String eventSource, final Map<String, Object> data) { | ||
| super(eventSource); | ||
| this.data = Collections.unmodifiableMap(new LinkedHashMap<>(data)); | ||
| } | ||
|
|
||
| @Override | ||
| public Map<String, Object> getData() { | ||
| return data; | ||
| } | ||
| } |
| @@ -0,0 +1,39 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| class StandardEventLogger extends AbstractEventLogger implements EventLogger { | ||
|
|
||
| private final EventWriter writer; | ||
|
|
||
| StandardEventLogger(final String eventSource, final EventWriter writer) { | ||
| super(eventSource); | ||
| this.writer = writer; | ||
| } | ||
|
|
||
| @Override | ||
| void log(final Event event) { | ||
| writer.write(event); | ||
| } | ||
|
|
||
| } |
| @@ -0,0 +1,63 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.io.FileDescriptor; | ||
| import java.io.FileOutputStream; | ||
| import java.io.PrintStream; | ||
|
|
||
| /** | ||
| * An event writer which writes directly to {@code stdout}. | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| public class StdoutEventWriter implements EventWriter { | ||
|
|
||
| private static final PrintStream STDOUT = new PrintStream(new FileOutputStream(FileDescriptor.out), true); | ||
|
|
||
| private final EventFormatter formatter; | ||
|
|
||
| private StdoutEventWriter(final EventFormatter formatter) { | ||
| this.formatter = formatter; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new {@code stdout} event writer with the provided formatter. | ||
| * | ||
| * @param formatter the formatter to use for formatting the event | ||
| * | ||
| * @return a new {@code stdout} event writer | ||
| */ | ||
| public static StdoutEventWriter of(final EventFormatter formatter) { | ||
| return new StdoutEventWriter(formatter); | ||
| } | ||
|
|
||
| @Override | ||
| public void write(final Event event) { | ||
| final EventFormatter formatter = this.formatter; | ||
| STDOUT.println(formatter.format(event)); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| // Don't actually close, just flush | ||
| STDOUT.flush(); | ||
| } | ||
| } |
| @@ -0,0 +1,145 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.io.StringReader; | ||
| import java.math.BigDecimal; | ||
| import java.math.BigInteger; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.Collection; | ||
| import java.util.Iterator; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.Executors; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.function.BiFunction; | ||
| import java.util.function.Function; | ||
| import javax.json.Json; | ||
| import javax.json.JsonObject; | ||
| import javax.json.JsonReader; | ||
|
|
||
| import org.junit.Assert; | ||
| import org.wildfly.common.cpu.ProcessorInfo; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| abstract class AbstractEventLoggerTestCase { | ||
| static final long TIMEOUT = 5L; | ||
|
|
||
| static void testLogger(final EventLogger logger, final QueuedJsonWriter writer) throws Exception { | ||
| final ZonedDateTime now = ZonedDateTime.now(); | ||
|
|
||
| final TestValues values = new TestValues() | ||
| .add("eventSource", logger.getEventSource(), JsonObject::getString) | ||
| .add("testBoolean", true, JsonObject::getBoolean) | ||
| .add("testString", "Test string", JsonObject::getString) | ||
| .add("testInt", 33, JsonObject::getInt) | ||
| .add("testLong", 138L, (json, key) -> json.getJsonNumber(key).longValue()) | ||
| .add("testDouble", 6.50d, Double::doubleToLongBits, (json, key) -> Double.doubleToLongBits(json.getJsonNumber(key).doubleValue())) | ||
| .add("testDate", now, ZonedDateTime::toString, JsonObject::getString) | ||
| .add("testDecimal", new BigDecimal("33.50"), (json, key) -> json.getJsonNumber(key).bigDecimalValue()) | ||
| .add("testBigInt", new BigInteger("8675309"), (json, key) -> json.getJsonNumber(key).bigIntegerValue()); | ||
|
|
||
| logger.log(values.asMap()); | ||
|
|
||
| final String jsonString = writer.events.poll(TIMEOUT, TimeUnit.SECONDS); | ||
| Assert.assertNotNull("Expected value written, but was null", jsonString); | ||
|
|
||
| try (JsonReader reader = Json.createReader(new StringReader(jsonString))) { | ||
| final JsonObject jsonObject = reader.readObject(); | ||
| for (TestValue<?> testValue : values) { | ||
| testValue.compare(jsonObject); | ||
| } | ||
| } | ||
|
|
||
| Assert.assertTrue("Expected no more events: " + writer.events, writer.events.isEmpty()); | ||
| } | ||
|
|
||
| static ExecutorService createExecutor() { | ||
| return Executors.newFixedThreadPool(Math.max(2, ProcessorInfo.availableProcessors() - 2)); | ||
| } | ||
|
|
||
| static class TestValue<V> { | ||
| final String key; | ||
| final V value; | ||
| final BiFunction<JsonObject, String, Object> mapper; | ||
| final Function<V, Object> valueConverter; | ||
|
|
||
| private TestValue(final String key, final V value, final BiFunction<JsonObject, String, Object> mapper) { | ||
| this(key, value, (v) -> v, mapper); | ||
| } | ||
|
|
||
| private TestValue(final String key, final V value, final Function<V, Object> valueConverter, final BiFunction<JsonObject, String, Object> mapper) { | ||
| this.key = key; | ||
| this.value = value; | ||
| this.valueConverter = valueConverter; | ||
| this.mapper = mapper; | ||
| } | ||
|
|
||
| void compare(final JsonObject json) { | ||
| Assert.assertEquals(valueConverter.apply(value), mapper.apply(json, key)); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "TestValue[key=" + key + ", value=" + value + "]"; | ||
| } | ||
| } | ||
|
|
||
| static class TestValues implements Iterable<TestValue<?>> { | ||
| private final Collection<TestValue<?>> values; | ||
|
|
||
| TestValues() { | ||
| values = new ArrayList<>(); | ||
| } | ||
|
|
||
| <V> TestValues add(final String key, final V value, final BiFunction<JsonObject, String, Object> mapper) { | ||
| values.add(new TestValue<>(key, value, mapper)); | ||
| return this; | ||
| } | ||
|
|
||
| <V> TestValues add(final String key, final V value, final Function<V, Object> valueConverter, final BiFunction<JsonObject, String, Object> mapper) { | ||
| values.add(new TestValue<>(key, value, valueConverter, mapper)); | ||
| return this; | ||
| } | ||
|
|
||
| Map<String, Object> asMap() { | ||
| final Map<String, Object> result = new LinkedHashMap<>(); | ||
| for (TestValue<?> testValue : values) { | ||
| result.put(testValue.key, testValue.value); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| @SuppressWarnings("NullableProblems") | ||
| @Override | ||
| public Iterator<TestValue<?>> iterator() { | ||
| return values.iterator(); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "TestValues[values=" + values + "]"; | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,135 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.io.StringReader; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.Random; | ||
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.Executors; | ||
| import java.util.concurrent.TimeUnit; | ||
| import javax.json.Json; | ||
| import javax.json.JsonObject; | ||
| import javax.json.JsonReader; | ||
|
|
||
| import org.junit.Assert; | ||
| import org.junit.Test; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| @SuppressWarnings("MagicNumber") | ||
| public class AsyncEventLoggerTestCase extends AbstractEventLoggerTestCase { | ||
|
|
||
| @Test | ||
| public void testLogger() throws Exception { | ||
| final ExecutorService executor = Executors.newSingleThreadExecutor(); | ||
| try { | ||
| final QueuedJsonWriter writer = new QueuedJsonWriter(); | ||
| final EventLogger logger = EventLogger.createAsyncLogger("test-async-logger", writer, executor); | ||
| testLogger(logger, writer); | ||
| } finally { | ||
| executor.shutdown(); | ||
| Assert.assertTrue(String.format("Executed did not complete within %d seconds", TIMEOUT), | ||
| executor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testMultiLogger() throws Exception { | ||
| final ExecutorService executor = createExecutor(); | ||
| try { | ||
| final QueuedJsonWriter writer = new QueuedJsonWriter(); | ||
| final EventLogger logger = EventLogger.createAsyncLogger("test=multi-async-logger", writer, executor); | ||
| testMultiLogger(logger, writer, 100, true); | ||
| } finally { | ||
| executor.shutdown(); | ||
| Assert.assertTrue(String.format("Executed did not complete within %d seconds", TIMEOUT), | ||
| executor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testMultiFloodLogger() throws Exception { | ||
| final ExecutorService executor = createExecutor(); | ||
| try { | ||
| final QueuedJsonWriter writer = new QueuedJsonWriter(); | ||
| final EventLogger logger = EventLogger.createAsyncLogger("test=multi-async-logger", writer, executor); | ||
| testMultiLogger(logger, writer, 10000, false); | ||
| } finally { | ||
| executor.shutdown(); | ||
| Assert.assertTrue(String.format("Executed did not complete within %d seconds", TIMEOUT), | ||
| executor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)); | ||
| } | ||
| } | ||
|
|
||
| private static void testMultiLogger(final EventLogger logger, final QueuedJsonWriter writer, final int logCount, | ||
| final boolean sleep) throws Exception { | ||
| final Random r = new Random(); | ||
| final ExecutorService executor = createExecutor(); | ||
| try { | ||
| final Map<Integer, TestValues> createValues = new HashMap<>(); | ||
| for (int i = 0; i < logCount; i++) { | ||
| final TestValues values = new TestValues() | ||
| .add("eventSource", logger.getEventSource(), JsonObject::getString) | ||
| .add("count", i, JsonObject::getInt); | ||
| createValues.put(i, values); | ||
| // Use a supplier for every 5th event | ||
| final boolean useSupplier = (i % 5 == 0); | ||
| executor.submit(() -> { | ||
| if (useSupplier) { | ||
| logger.log(values::asMap); | ||
| } else { | ||
| logger.log(values.asMap()); | ||
| } | ||
| if (sleep) { | ||
| // Add a short sleep to ensure slower messages still make it through and aren't lost | ||
| try { | ||
| TimeUnit.MILLISECONDS.sleep(r.nextInt(150)); | ||
| } catch (InterruptedException e) { | ||
| Assert.fail("Interrupted running thread " + Thread.currentThread().getName() + ": " + e.getMessage()); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| for (int i = 0; i < logCount; i++) { | ||
| final String jsonString = writer.events.poll(TIMEOUT, TimeUnit.SECONDS); | ||
| Assert.assertNotNull("Expected value written, but was null", jsonString); | ||
|
|
||
| try (JsonReader reader = Json.createReader(new StringReader(jsonString))) { | ||
| final JsonObject jsonObject = reader.readObject(); | ||
| final int count = jsonObject.getInt("count"); | ||
| final TestValues values = createValues.remove(count); | ||
| Assert.assertNotNull("Failed to find value for entry " + count, values); | ||
| for (TestValue<?> testValue : values) { | ||
| testValue.compare(jsonObject); | ||
| } | ||
| } | ||
| } | ||
| Assert.assertTrue("Values were created that were not logged: " + createValues, createValues.isEmpty()); | ||
| } finally { | ||
| executor.shutdown(); | ||
| Assert.assertTrue(String.format("Executed did not complete within %d seconds", TIMEOUT), | ||
| executor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)); | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,47 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.util.concurrent.BlockingDeque; | ||
| import java.util.concurrent.LinkedBlockingDeque; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| public class QueuedJsonWriter implements EventWriter { | ||
|
|
||
| private final JsonEventFormatter formatter; | ||
|
|
||
| final BlockingDeque<String> events = new LinkedBlockingDeque<>(); | ||
|
|
||
| QueuedJsonWriter() { | ||
| this.formatter = JsonEventFormatter.builder().build(); | ||
| } | ||
|
|
||
| @Override | ||
| public void write(final Event event) { | ||
| events.add(formatter.format(event)); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| events.clear(); | ||
| } | ||
| } |
| @@ -0,0 +1,179 @@ | ||
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * | ||
| * Copyright 2019 Red Hat, Inc., and individual contributors | ||
| * as indicated by the @author tags. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.event.logger; | ||
|
|
||
| import java.io.StringReader; | ||
| import java.util.Arrays; | ||
| import java.util.HashMap; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.TimeUnit; | ||
| import javax.json.Json; | ||
| import javax.json.JsonArray; | ||
| import javax.json.JsonObject; | ||
| import javax.json.JsonReader; | ||
|
|
||
| import org.junit.Assert; | ||
| import org.junit.Test; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| public class StandardEventLoggerTestCase extends AbstractEventLoggerTestCase { | ||
| private static final int LOG_COUNT = 10000; | ||
|
|
||
| @Test | ||
| public void testLogger() throws Exception { | ||
| final QueuedJsonWriter writer = new QueuedJsonWriter(); | ||
| final EventLogger logger = EventLogger.createLogger("test-logger", writer); | ||
| testLogger(logger, writer); | ||
| } | ||
|
|
||
| @Test | ||
| public void testMultiLogger() throws Exception { | ||
| final QueuedJsonWriter writer = new QueuedJsonWriter(); | ||
| final EventLogger logger = EventLogger.createLogger("test-multi-logger", writer); | ||
| testMultiLogger(logger, writer); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCollection() throws Exception { | ||
| final QueuedJsonWriter writer = new QueuedJsonWriter(); | ||
| final EventLogger logger = EventLogger.createLogger("test-collection-logger", writer); | ||
| final List<String> expectedValues = Arrays.asList("a", "b", "c", "1", "2", "3"); | ||
| final Map<String, Object> events = new LinkedHashMap<>(); | ||
| events.put("testCollection", expectedValues); | ||
| logger.log(events); | ||
|
|
||
| final String jsonString = writer.events.poll(TIMEOUT, TimeUnit.SECONDS); | ||
| Assert.assertNotNull("Expected value written, but was null", jsonString); | ||
|
|
||
| try (JsonReader reader = Json.createReader(new StringReader(jsonString))) { | ||
| final JsonObject jsonObject = reader.readObject(); | ||
| final JsonArray array = jsonObject.getJsonArray("testCollection"); | ||
| Assert.assertEquals(expectedValues.size(), array.size()); | ||
| for (int i = 0; i < expectedValues.size(); i++) { | ||
| Assert.assertEquals(expectedValues.get(i), array.getString(i)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testMap() throws Exception { | ||
| final QueuedJsonWriter writer = new QueuedJsonWriter(); | ||
| final EventLogger logger = EventLogger.createLogger("test-map-logger", writer); | ||
| final Map<String, String> expectedValues = new LinkedHashMap<>(); | ||
| expectedValues.put("key1", "value1"); | ||
| expectedValues.put("key2", "value2"); | ||
| expectedValues.put("key3", "value3"); | ||
| final Map<String, Object> events = new LinkedHashMap<>(); | ||
| events.put("testMap", expectedValues); | ||
| logger.log(events); | ||
|
|
||
| final String jsonString = writer.events.poll(TIMEOUT, TimeUnit.SECONDS); | ||
| Assert.assertNotNull("Expected value written, but was null", jsonString); | ||
|
|
||
| try (JsonReader reader = Json.createReader(new StringReader(jsonString))) { | ||
| final JsonObject jsonObject = reader.readObject(); | ||
| final JsonObject value = jsonObject.getJsonObject("testMap"); | ||
| Assert.assertEquals(expectedValues.size(), value.size()); | ||
| Assert.assertEquals(expectedValues.get("key1"), value.getString("key1")); | ||
| Assert.assertEquals(expectedValues.get("key2"), value.getString("key2")); | ||
| Assert.assertEquals(expectedValues.get("key3"), value.getString("key3")); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testArrays() throws Exception { | ||
| final QueuedJsonWriter writer = new QueuedJsonWriter(); | ||
| final EventLogger logger = EventLogger.createLogger("test-array-logger", writer); | ||
| final Integer[] expectedIntValues = new Integer[] {1, 2, 3, 4, 5, 6}; | ||
| final Map<String, Object> events = new LinkedHashMap<>(); | ||
| events.put("testIntArray", expectedIntValues); | ||
| final String[] expectedStringValues = new String[] {"a", "b", "c"}; | ||
| events.put("testStringArray", expectedStringValues); | ||
| logger.log(events); | ||
|
|
||
| final String jsonString = writer.events.poll(TIMEOUT, TimeUnit.SECONDS); | ||
| Assert.assertNotNull("Expected value written, but was null", jsonString); | ||
|
|
||
| try (JsonReader reader = Json.createReader(new StringReader(jsonString))) { | ||
| final JsonObject jsonObject = reader.readObject(); | ||
|
|
||
| // Test int values | ||
| JsonArray array = jsonObject.getJsonArray("testIntArray"); | ||
| Assert.assertEquals(expectedIntValues.length, array.size()); | ||
| for (int i = 0; i < expectedIntValues.length; i++) { | ||
| Assert.assertEquals((int) expectedIntValues[i], array.getInt(i)); | ||
| } | ||
|
|
||
| // Test string values | ||
| array = jsonObject.getJsonArray("testStringArray"); | ||
| Assert.assertEquals(expectedStringValues.length, array.size()); | ||
| for (int i = 0; i < expectedStringValues.length; i++) { | ||
| Assert.assertEquals(expectedStringValues[i], array.getString(i)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static void testMultiLogger(final EventLogger logger, final QueuedJsonWriter writer) throws Exception { | ||
| final ExecutorService executor = createExecutor(); | ||
| try { | ||
| final Map<Integer, TestValues> createValues = new HashMap<>(); | ||
| for (int i = 0; i < LOG_COUNT; i++) { | ||
| final TestValues values = new TestValues() | ||
| .add("eventSource", logger.getEventSource(), JsonObject::getString) | ||
| .add("count", i, JsonObject::getInt); | ||
| createValues.put(i, values); | ||
| // Use a supplier for every 5th event | ||
| final boolean useSupplier = (i % 5 == 0); | ||
| executor.submit(() -> { | ||
| if (useSupplier) { | ||
| logger.log(values::asMap); | ||
| } else { | ||
| logger.log(values.asMap()); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| for (int i = 0; i < LOG_COUNT; i++) { | ||
| final String jsonString = writer.events.poll(TIMEOUT, TimeUnit.SECONDS); | ||
| Assert.assertNotNull("Expected value written, but was null", jsonString); | ||
|
|
||
| try (JsonReader reader = Json.createReader(new StringReader(jsonString))) { | ||
| final JsonObject jsonObject = reader.readObject(); | ||
| final int count = jsonObject.getInt("count"); | ||
| final TestValues values = createValues.remove(count); | ||
| Assert.assertNotNull("Failed to find value for entry " + count, values); | ||
| for (TestValue<?> testValue : values) { | ||
| testValue.compare(jsonObject); | ||
| } | ||
| } | ||
| } | ||
| Assert.assertTrue("Values were created that were not logged: " + createValues, createValues.isEmpty()); | ||
| } finally { | ||
| executor.shutdown(); | ||
| Assert.assertTrue(String.format("Executed did not complete within %d seconds", TIMEOUT), | ||
| executor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)); | ||
| } | ||
| } | ||
| } |