Skip to content

Commit

Permalink
Initial take at Structured Audit Log support
Browse files Browse the repository at this point in the history
  • Loading branch information
ar committed Jun 12, 2024
1 parent b52a0cf commit 819193d
Show file tree
Hide file tree
Showing 44 changed files with 1,308 additions and 109 deletions.
10 changes: 10 additions & 0 deletions jpos/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
requires micrometer.core;
requires micrometer.registry.prometheus;
requires org.apache.sshd;
requires com.fasterxml.jackson.annotation;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.datatype.jsr310;
requires com.fasterxml.jackson.dataformat.xml;
// requires net.i2p.crypto.eddsa;

exports org.jpos.iso.packager;
Expand Down Expand Up @@ -45,6 +49,12 @@
exports org.jpos.core;
exports org.jpos.core.handlers.exception;
exports org.jpos.rc;
exports org.jpos.log;
exports org.jpos.log.render.xml;
exports org.jpos.log.render.json;
exports org.jpos.log.render.markdown;
exports org.jpos.log.evt;

uses org.jpos.core.EnvironmentProvider;
uses org.jpos.log.LogRenderer;
}
38 changes: 38 additions & 0 deletions jpos/src/main/java/org/jpos/log/AuditLogEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2023 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.jpos.log;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.jpos.log.evt.*;

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "t"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Start.class, name = "start"),
@JsonSubTypes.Type(value = Stop.class, name = "stop"),
@JsonSubTypes.Type(value = Deploy.class, name = "deploy"),
@JsonSubTypes.Type(value = UnDeploy.class, name = "undeploy"),
@JsonSubTypes.Type(value = LogMessage.class, name = "msg")
})

public sealed interface AuditLogEvent permits LogMessage, Deploy, UnDeploy, Start, Stop { }
48 changes: 48 additions & 0 deletions jpos/src/main/java/org/jpos/log/LogRenderer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.jpos.log;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

public interface LogRenderer<T> {
void render (T obj, PrintStream ps, String indent);
Class<?> clazz();
Type type();

default void render (T obj, PrintStream ps) {
render (obj, ps, "");
}

default String render (T obj, String indent) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
render (obj, ps, indent);
return baos.toString();
}

default String render (T obj) {
return render (obj, "");
}

default String indent (String indent, String s) {
if (s == null || s.isEmpty() || indent==null || indent.isEmpty()) {
return s;
}
String[] lines = s.split("\n", -1); // Preserve trailing empty strings
StringBuilder indentedString = new StringBuilder();
for (int i = 0; i < lines.length; i++) {
indentedString.append(indent).append(lines[i]);
if (i < lines.length - 1) {
indentedString.append("\n");
}
}
return indentedString.toString();
}


enum Type {
XML,
JSON,
TXT,
MARKDOWN
}
}
138 changes: 138 additions & 0 deletions jpos/src/main/java/org/jpos/log/LogRendererRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package org.jpos.log;

import java.io.PrintStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
* A registry for managing {@link LogRenderer} instances associated with specific class types and renderer types.
* This class allows for the registration, retrieval, and management of {@link LogRenderer} instances dynamically,
* using a thread-safe approach to ensure proper operation in multi-threaded environments.
*/
public class LogRendererRegistry {
private static final Map<LogRendererRegistry.Key, LogRenderer<?>> renderers = Collections.synchronizedMap(
new LinkedHashMap<>()
);
static {
for (LogRenderer<?> r : ServiceLoader.load(LogRenderer.class)) {
register (r);
}
}

/**
* Registers a {@link LogRenderer} in the registry with a key generated from the renderer's class and type.
* @param renderer The renderer to register. Must not be null.
* @throws NullPointerException if the renderer is null.
*/
public static <T> void register (LogRenderer<?> renderer) {
Objects.requireNonNull(renderer);
renderers.put(new LogRendererRegistry.Key(renderer.clazz(), renderer.type()), renderer);
}

/**
* Dumps the current state of the registry to the specified {@link PrintStream}.
* @param ps The {@link PrintStream} to which the dump will be written, e.g.: System.out
*/
public static void dump (PrintStream ps) {
ps.println (LogRendererRegistry.class);
for (Map.Entry<LogRendererRegistry.Key, LogRenderer<?>> entry : renderers.entrySet()) {
ps.println(" " + entry.getKey() + ": " + entry.getValue().getClass());
}
ps.println ();
}

/**
* Retrieves a {@link LogRenderer} that matches the specified class and type. If no direct match is found,
* it attempts to find a renderer for any superclass or implemented interfaces. If no specific renderer is found,
* it defaults to a renderer for {@link Object}, if present for the given type.
*
* @param clazz The class for which a renderer is required.
* @param type The type of the renderer.
* @return The matching {@link LogRenderer}, or a default renderer if no specific match is found.
*/
@SuppressWarnings("unchecked")
public static <T> LogRenderer<T> getRenderer(Class<?> clazz, LogRenderer.Type type) {
LogRenderer<T> renderer = getRendererForClass(clazz, type);
boolean needsCache = false;
if (renderer == null) {
needsCache = true;
renderer = getRendererForInterface(clazz, type);
}
if (renderer == null) {
renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(Object.class, type));
}
if (renderer != null && needsCache)
renderers.put(new LogRendererRegistry.Key(clazz, renderer.type(), true), renderer);
return renderer;
}

/**
* Recursively searches for a renderer for the given class and type, considering superclasses.
*
* @param clazz The class for which a renderer is needed.
* @param type The type of the renderer.
* @return A matching renderer, or null if none is found.
*/
@SuppressWarnings("unchecked")
private static <T> LogRenderer<T> getRendererForClass (Class<?> clazz, LogRenderer.Type type) {
LogRenderer<T> renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(clazz, type));
if (renderer == null && clazz.getSuperclass() != Object.class) {
renderer = getRendererForClass(clazz.getSuperclass(), type);
}
return renderer;
}

/**
* Searches for a renderer among the interfaces implemented by the specified class.
*
* @param clazz The class whose interfaces will be checked for a matching renderer.
* @param type The type of the renderer.
* @return A matching renderer, or null if none is found.
*/
@SuppressWarnings("unchecked")
private static <T> LogRenderer<T> getRendererForInterface (Class<?> clazz, LogRenderer.Type type) {
for (Class<?> i : clazz.getInterfaces()) {
LogRenderer<T> renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(i, type));
if (renderer != null)
return renderer;
}
return null;
}

/**
* A private key class to encapsulate the combination of class type and renderer type.
* This key is used to uniquely identify renderers within the registry.
*/
private static class Key {
private final Class<?> clazz;
private final LogRenderer.Type type;
private final boolean cache;

public Key(Class<?> clazz, LogRenderer.Type type) {
this (clazz, type, false);
}
public Key(Class<?> clazz, LogRenderer.Type type, boolean cache) {
this.clazz = clazz;
this.type = type;
this.cache = cache;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LogRendererRegistry.Key key = (LogRendererRegistry.Key) o;
return Objects.equals(clazz, key.clazz) && type == key.type;
}

@Override
public int hashCode() {
return Objects.hash(clazz, type);
}

@Override
public String toString() {
return "Key{" + clazz + ", type=" + type + (cache ? ", cached" : "") + '}';
}
}
}
33 changes: 33 additions & 0 deletions jpos/src/main/java/org/jpos/log/evt/Deploy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2023 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.jpos.log.evt;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import org.jpos.log.AuditLogEvent;

/**
* Represents a deployment configuration with a specific path and an enabled status.
*
* <p>This record is used as part of the audit log events in the system to track deployment actions.</p>
*
* @param path the path where the deployment is located
* @param enabled a boolean flag indicating whether the deployment is enabled
*/

public record Deploy(String path, @JacksonXmlProperty(isAttribute = true) boolean enabled) implements AuditLogEvent { }
37 changes: 37 additions & 0 deletions jpos/src/main/java/org/jpos/log/evt/LogEvt.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2010 Alejandro P. Revilla
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.jpos.log.evt;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import org.jpos.log.AuditLogEvent;
import java.time.Instant;
import java.util.List;
import java.util.UUID;

@JacksonXmlRootElement(localName = "log")
public record LogEvt(
@JacksonXmlProperty(isAttribute = true) Instant ts,
@JacksonXmlProperty(isAttribute = true) @JsonProperty("trace-id") UUID traceId,
@JacksonXmlProperty(isAttribute = true) String realm,
@JacksonXmlProperty(isAttribute = true) String tag,
@JacksonXmlProperty(isAttribute = true) Long elapsed,
@JsonProperty("evt") @JacksonXmlElementWrapper(useWrapping = false) List<AuditLogEvent> events) { }
6 changes: 6 additions & 0 deletions jpos/src/main/java/org/jpos/log/evt/LogMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.jpos.log.evt;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.jpos.log.AuditLogEvent;

public record LogMessage(@JsonProperty("m") String msg) implements AuditLogEvent { }
36 changes: 36 additions & 0 deletions jpos/src/main/java/org/jpos/log/evt/Start.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2023 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.jpos.log.evt;

import org.jpos.log.AuditLogEvent;

import java.util.UUID;

/**
* Represents the starting log entry for an auditing process in the system. This record encapsulates
* all the essential details needed for initializing audit logs in a structured and consistent format.
*
* @param q2 The identifier of the Q2 system instance from which the log is originating.
* @param version The version of the Q2 system, detailing the specific build or release version.
* @param appVersion The version of the application that is running within the Q2 system,
* providing context about the application's release state.
* @param deploy Absolute path to Q2's deploy directory.
* @param env The name of the environment in which the application is running.
*/
public record Start(UUID q2, String version, String appVersion, String deploy, String env) implements AuditLogEvent { }
Loading

0 comments on commit 819193d

Please sign in to comment.