001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.log.render.json;
020
021import com.fasterxml.jackson.annotation.JsonInclude;
022import com.fasterxml.jackson.core.JsonProcessingException;
023import com.fasterxml.jackson.databind.ObjectMapper;
024import com.fasterxml.jackson.databind.module.SimpleModule;
025import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
026import org.jpos.log.AuditLogEvent;
027import org.jpos.log.AuditLogEventRegistry;
028
029import org.jpos.log.LogRenderer;
030import org.jpos.log.evt.LogEvt;
031import org.jpos.log.evt.LogMessage;
032import org.jpos.log.evt.ThrowableAuditLogEvent;
033import org.jpos.log.render.ThrowableSerializer;
034import org.jpos.util.LogEvent;
035import org.jpos.util.Loggeable;
036
037import java.io.ByteArrayOutputStream;
038import java.io.PrintStream;
039import java.time.Duration;
040import java.util.LinkedHashMap;
041import java.util.List;
042import java.util.Map;
043
044/** JSON renderer for {@link LogEvent}, serialising tag/realm/payload via Jackson. */
045public final class LogEventJsonLogRenderer implements LogRenderer<LogEvent> {
046    private final ObjectMapper mapper = new ObjectMapper();
047
048    /** Default constructor. */
049    public LogEventJsonLogRenderer() {
050        mapper.registerModule(new JavaTimeModule());
051        mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
052        // mapper.enable(SerializationFeature.INDENT_OUTPUT);
053        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
054        SimpleModule module = new SimpleModule();
055        module.addSerializer(Throwable.class, new ThrowableSerializer());
056        mapper.registerModule(module);
057        AuditLogEventRegistry.register(mapper);
058    }
059
060    @Override
061    public void render(LogEvent evt, PrintStream ps, String indent) {
062        List<AuditLogEvent> events = evt.getPayLoad()
063          .stream()
064          .map (obj -> switch (obj) {
065              case AuditLogEvent ale -> ale;
066              case Throwable t -> new ThrowableAuditLogEvent(t);
067              default -> new LogMessage(dump(obj));
068          }).toList();
069
070        long elapsed = Duration.between(evt.getCreatedAt(), evt.getDumpedAt()).toMillis();
071        LogEvt ev = new LogEvt (
072          evt.getDumpedAt(),
073          evt.getTag(),
074          elapsed == 0L ? null : elapsed,
075          buildTags(evt),
076          events
077        );
078        try {
079             ps.println (mapper.writeValueAsString(ev));
080        } catch (JsonProcessingException e) {
081            throw new RuntimeException (e);
082        }
083    }
084    public Class<?> clazz() {
085        return LogEvent.class;
086    }
087    public Type type() {
088        return Type.JSON;
089    }
090
091    private Map<String,String> buildTags(LogEvent evt) {
092        evt.getTraceId(); // ensure trace-id is generated
093        Map<String,String> tags = new LinkedHashMap<>();
094        String realm = evt.getRealm();
095        if (realm != null && !realm.isEmpty())
096            tags.put("realm", realm);
097        tags.putAll(evt.getTags());
098        return tags;
099    }
100
101    private String dump (Object obj) {
102        if (obj instanceof Loggeable loggeable) {
103            ByteArrayOutputStream baos = new ByteArrayOutputStream();
104            PrintStream ps = new PrintStream(baos);
105            loggeable.dump(ps, "");
106            return baos.toString();
107        }
108        return obj.getClass() + ":" + obj;
109    }
110}