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.xml;
020
021import com.fasterxml.jackson.core.JsonProcessingException;
022import com.fasterxml.jackson.databind.SerializationFeature;
023import com.fasterxml.jackson.databind.module.SimpleModule;
024import com.fasterxml.jackson.dataformat.xml.XmlMapper;
025import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
026import org.jpos.log.AuditLogEvent;
027
028import org.jpos.log.LogRenderer;
029import org.jpos.log.evt.LogEvt;
030import org.jpos.log.evt.LogMessage;
031import org.jpos.log.evt.ThrowableAuditLogEvent;
032import org.jpos.log.render.ThrowableSerializer;
033import org.jpos.util.LogEvent;
034import org.jpos.util.Loggeable;
035
036import java.io.ByteArrayOutputStream;
037import java.io.PrintStream;
038import java.time.Duration;
039import java.util.List;
040
041public final class LogEventXmlLogRenderer implements LogRenderer<LogEvent> {
042    private final XmlMapper mapper = new XmlMapper();
043
044    public LogEventXmlLogRenderer() {
045        mapper.registerModule(new JavaTimeModule());
046        mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
047
048        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
049        mapper.enable(SerializationFeature.INDENT_OUTPUT);
050
051        SimpleModule module = new SimpleModule();
052        module.addSerializer(Throwable.class, new ThrowableSerializer());
053        mapper.registerModule(module);
054    }
055
056    @Override
057    public void render(LogEvent evt, PrintStream ps, String indent) {
058        List<AuditLogEvent> events = evt.getPayLoad()
059          .stream()
060          .map (obj -> switch (obj) {
061              case AuditLogEvent ale -> ale;
062              case Throwable t -> new ThrowableAuditLogEvent(t);
063              default -> new LogMessage(dump(obj));
064          }).toList();
065        long elapsed = Duration.between(evt.getCreatedAt(), evt.getDumpedAt()).toMillis();
066        LogEvt ev = new LogEvt (
067          evt.getDumpedAt(),
068          evt.getTraceId(),
069          evt.getRealm(),
070          evt.getTag(),
071          elapsed == 0L ? null : elapsed,
072          events
073        );
074        try {
075            ps.println (mapper.writeValueAsString(ev));
076        } catch (JsonProcessingException e) {
077            ps.print (kv("exception", e.toString()));
078        }
079    }
080    public Class<?> clazz() {
081        return LogEvent.class;
082    }
083    public Type type() {
084        return Type.XML;
085    }
086
087    private String kv (String k, String v) {
088        return "{\"%s\":\"%s\"}".formatted(k,v);
089    }
090
091    private String dump (Object obj) {
092        if (obj instanceof Loggeable loggeable) {
093            ByteArrayOutputStream baos = new ByteArrayOutputStream();
094            PrintStream ps = new PrintStream(baos);
095            loggeable.dump(ps, "");
096            return baos.toString();
097        }
098        return obj.toString();
099    }
100}