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.util; 020 021import org.jdom2.Element; 022import org.jdom2.output.Format; 023import org.jdom2.output.XMLOutputter; 024import org.jpos.jfr.LogEventDump; 025import org.jpos.log.LogRenderer; 026import org.jpos.log.LogRendererRegistry; 027import org.jpos.log.evt.SysInfo; 028import org.jpos.log.render.txt.SysInfoTxtLogRenderer; 029 030import java.io.ByteArrayOutputStream; 031import java.io.IOException; 032import java.io.PrintStream; 033import java.sql.SQLException; 034import java.time.Duration; 035import java.time.Instant; 036import java.time.LocalDateTime; 037import java.time.ZoneId; 038import java.util.*; 039 040/** 041 * @author @apr 042 */ 043public class LogEvent { 044 private LogSource source; 045 private String tag; 046 private final List<Object> payLoad; 047 private Instant createdAt; 048 private Instant dumpedAt; 049 private boolean honorSourceLogger; 050 private boolean noArmor; 051 private boolean hasException; 052 private String traceId; 053 054 public LogEvent (String tag) { 055 super(); 056 this.tag = tag; 057 createdAt = Instant.now(); 058 this.payLoad = Collections.synchronizedList (new ArrayList<>()); 059 } 060 061 public LogEvent () { 062 this("info"); 063 } 064 public LogEvent (String tag, Object msg) { 065 this (tag); 066 addMessage(msg); 067 } 068 public LogEvent (LogSource source, String tag) { 069 this (tag); 070 this.source = source; 071 honorSourceLogger = true; 072 } 073 public LogEvent (LogSource source, String tag, Object msg) { 074 this (tag); 075 this.source = source; 076 honorSourceLogger = true; 077 addMessage(msg); 078 } 079 public String getTag() { 080 return tag; 081 } 082 public void setTag (String tag) { 083 this.tag = tag; 084 } 085 public void addMessage (Object msg) { 086 payLoad.add (msg); 087 if (msg instanceof Throwable) 088 hasException = true; 089 } 090 public void addMessage (String tagname, String message) { 091 payLoad.add ("<"+tagname+">"+message+"</"+tagname+">"); 092 } 093 public LogSource getSource() { 094 return source; 095 } 096 public void setSource(LogSource source) { 097 this.source = source; 098 } 099 public void setNoArmor (boolean noArmor) { 100 this.noArmor = noArmor; 101 } 102 protected String dumpHeader (PrintStream p, String indent) { 103 if (noArmor) { 104 p.println(""); 105 } else { 106 dumpedAt = getDumpedAt(); 107 StringBuilder sb = new StringBuilder(indent); 108 sb.append ("<log realm=\""); 109 sb.append (getRealm()); 110 sb.append("\" at=\""); 111 sb.append(LocalDateTime.ofInstant(dumpedAt, ZoneId.systemDefault())); 112 sb.append ('"'); 113 long elapsed = Duration.between(createdAt, dumpedAt).toMillis(); 114 if (elapsed > 0) { 115 sb.append (" lifespan=\""); 116 sb.append (elapsed); 117 sb.append ("ms\""); 118 } 119 if (traceId != null) { 120 sb.append (String.format (" trace-id=\"%s\"", traceId)); 121 } 122 sb.append ('>'); 123 p.println (sb); 124 } 125 return indent + " "; 126 } 127 protected void dumpTrailer (PrintStream p, String indent) { 128 if (!noArmor) 129 p.println (indent + "</log>"); 130 } 131 public void dump (PrintStream p, String outer) { 132 var jfr = new LogEventDump(); 133 jfr.begin(); 134 try { 135 String indent = dumpHeader (p, outer); 136 if (payLoad.isEmpty()) { 137 if (tag != null) 138 p.println (indent + "<" + tag + "/>"); 139 } 140 else { 141 String newIndent; 142 if (tag != null) { 143 if (!tag.isEmpty()) 144 p.println (indent + "<" + tag + ">"); 145 newIndent = indent + " "; 146 } 147 else 148 newIndent = ""; 149 synchronized (payLoad) { 150 for (Object o : payLoad) { 151 if (o instanceof Loggeable) 152 ((Loggeable) o).dump(p, newIndent); 153 else if (o instanceof SQLException) { 154 SQLException e = (SQLException) o; 155 p.println(newIndent + "<SQLException>" 156 + e.getMessage() + "</SQLException>"); 157 p.println(newIndent + "<SQLState>" 158 + e.getSQLState() + "</SQLState>"); 159 p.println(newIndent + "<VendorError>" 160 + e.getErrorCode() + "</VendorError>"); 161 ((Throwable) o).printStackTrace(p); 162 } else if (o instanceof Throwable) { 163 p.println(newIndent + "<exception name=\"" 164 + ((Throwable) o).getMessage() + "\">"); 165 p.print(newIndent); 166 ((Throwable) o).printStackTrace(p); 167 p.println(newIndent + "</exception>"); 168 } else if (o instanceof Object[]) { 169 Object[] oa = (Object[]) o; 170 p.print(newIndent + "["); 171 for (int j = 0; j < oa.length; j++) { 172 if (j > 0) 173 p.print(","); 174 p.print(oa[j].toString()); 175 } 176 p.println("]"); 177 } else if (o instanceof Element) { 178 p.println(""); 179 XMLOutputter out = new XMLOutputter(Format.getPrettyFormat()); 180 out.getFormat().setLineSeparator("\n"); 181 try { 182 out.output((Element) o, p); 183 } catch (IOException ex) { 184 ex.printStackTrace(p); 185 } 186 p.println(""); 187 } else if (o != null) { 188 LogRenderer<Object> renderer = LogRendererRegistry.getRenderer(o.getClass(), LogRenderer.Type.TXT); 189 if (renderer != null) 190 renderer.render(o, p, newIndent); 191 else 192 p.println(newIndent + o); 193 } else { 194 p.println(newIndent + "null"); 195 } 196 } 197 } 198 if (tag != null && !tag.isEmpty()) 199 p.println (indent + "</" + tag + ">"); 200 } 201 } catch (Throwable t) { 202 t.printStackTrace(p); 203 204 } finally { 205 dumpTrailer (p, outer); 206 jfr.commit(); 207 } 208 } 209 public String getRealm() { 210 return source != null ? source.getRealm() : ""; 211 } 212 public LogEvent withTraceId (String traceId) { 213 this.traceId = traceId; 214 return this; 215 } 216 public LogEvent withTraceId (UUID uuid) { 217 this.traceId = uuid.toString().replace("-", ""); 218 return this; 219 } 220 public LogEvent withSource (LogSource source) { 221 setSource(source); 222 return this; 223 } 224 public LogEvent add (Object o) { 225 addMessage(o); 226 return this; 227 } 228 public LogEvent withTraceId () { 229 getTraceId(); 230 return this; 231 } 232 public String getTraceId() { 233 synchronized(getPayLoad()) { 234 if (traceId == null) 235 traceId = UUID.randomUUID().toString().replace("-",""); 236 return traceId; 237 } 238 } 239 240 /** 241 * WARNING: payLoad is a SynchronizedList. If you intend to get a reference 242 * to it in order to iterate over the list, you need to synchronize on the 243 * returned object. 244 * 245 * <pre> 246 * synchronized (evt.getPayLoad()) { 247 * Iterator iter = evt.getPayLoad().iterator(); 248 * while (iter.hasNext()) { 249 * ... 250 * ... 251 * 252 * } 253 * } 254 * </pre> 255 * @return payLoad, which is a SynchronizedList 256 */ 257 public List<Object> getPayLoad() { 258 return payLoad; 259 } 260 public String toString(String indent) { 261 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 262 PrintStream p = new PrintStream (baos); 263 synchronized (getPayLoad()) { 264 dump(p, indent); 265 } 266 return baos.toString(); 267 } 268 public String toString() { 269 return toString(""); 270 } 271 272 public boolean hasException() { 273 return hasException; 274 } 275 /** 276 * This is a hack for backward compatibility after accepting PR67 277 * @see <a href="https://github.com/jpos/jPOS/pull/67">PR67</a> 278 * @return true if ISOSource has been set 279 */ 280 public boolean isHonorSourceLogger() { 281 return honorSourceLogger; 282 } 283 284 public synchronized Instant getDumpedAt() { 285 if (dumpedAt == null) 286 dumpedAt = Instant.now(); 287 return dumpedAt; 288 } 289 public Instant getCreatedAt() { 290 return createdAt; 291 } 292}