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; 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/** XML renderer for {@link LogEvent}, emitting wrapped {@code <log>} elements with payload children. */ 045public final class LogEventXmlLogRenderer implements LogRenderer<LogEvent> { 046 private final XmlMapper mapper = new XmlMapper(); 047 048 /** Default constructor. */ 049 public LogEventXmlLogRenderer() { 050 mapper.registerModule(new JavaTimeModule()); 051 mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 052 053 mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 054 mapper.enable(SerializationFeature.INDENT_OUTPUT); 055 056 SimpleModule module = new SimpleModule(); 057 module.addSerializer(Throwable.class, new ThrowableSerializer()); 058 mapper.registerModule(module); 059 AuditLogEventRegistry.register(mapper); 060 } 061 062 @Override 063 public void render(LogEvent evt, PrintStream ps, String indent) { 064 List<AuditLogEvent> events = evt.getPayLoad() 065 .stream() 066 .map (obj -> switch (obj) { 067 case AuditLogEvent ale -> ale; 068 case Throwable t -> new ThrowableAuditLogEvent(t); 069 default -> new LogMessage(dump(obj)); 070 }).toList(); 071 long elapsed = Duration.between(evt.getCreatedAt(), evt.getDumpedAt()).toMillis(); 072 LogEvt ev = new LogEvt ( 073 evt.getDumpedAt(), 074 evt.getTag(), 075 elapsed == 0L ? null : elapsed, 076 buildTags(evt), 077 events 078 ); 079 try { 080 ps.println (mapper.writeValueAsString(ev)); 081 } catch (JsonProcessingException e) { 082 ps.print (kv("exception", e.toString())); 083 } 084 } 085 public Class<?> clazz() { 086 return LogEvent.class; 087 } 088 public Type type() { 089 return Type.XML; 090 } 091 092 private Map<String,String> buildTags(LogEvent evt) { 093 evt.getTraceId(); // ensure trace-id is generated 094 Map<String,String> tags = new LinkedHashMap<>(); 095 String realm = evt.getRealm(); 096 if (realm != null && !realm.isEmpty()) 097 tags.put("realm", realm); 098 tags.putAll(evt.getTags()); 099 return tags; 100 } 101 102 private String kv (String k, String v) { 103 return "{\"%s\":\"%s\"}".formatted(k,v); 104 } 105 106 private String dump (Object obj) { 107 if (obj instanceof Loggeable loggeable) { 108 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 109 PrintStream ps = new PrintStream(baos); 110 loggeable.dump(ps, ""); 111 return baos.toString(); 112 } 113 return obj.toString(); 114 } 115}