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.markdown; 020 021import org.jpos.log.LogRenderer; 022import org.jpos.log.evt.KV; 023import org.jpos.log.evt.ProcessOutput; 024import org.jpos.log.evt.SysInfo; 025 026import java.io.PrintStream; 027import java.lang.reflect.InvocationTargetException; 028import java.lang.reflect.Method; 029import java.lang.reflect.RecordComponent; 030import java.util.List; 031import java.util.Map; 032import java.util.stream.Collectors; 033import java.util.stream.Stream; 034 035 036/** Markdown renderer for {@link SysInfo} audit records. */ 037public final class SysInfoMarkdownRenderer implements LogRenderer<SysInfo> { 038 /** Default constructor; no instance state to initialise. */ 039 public SysInfoMarkdownRenderer() {} 040 041 @Override 042 public void render(SysInfo sysinfo, PrintStream ps, String indent) { 043 Map<String,Object> entries = extractEntriesToMap(sysinfo); 044 int width = maxLength(entries); 045 final String fmt = "| %-" + width + "s | %s |%n"; 046 ps.println ("### SystemMonitor"); 047 ps.print (row(fmt, "id", "value")); 048 ps.print (row(fmt, "-".repeat(width), "-----")); 049 entries.forEach((k, v) -> { 050 switch (k) { 051 case "nameRegistrarEntries": 052 case "threads": 053 case "scripts": 054 break; 055 default: 056 ps.print( 057 row(fmt, k, v.toString()) 058 ); 059 } 060 }); 061 renderNameRegistrar(sysinfo.nameRegistrarEntries(), ps, fmt, width); 062 renderThreads(sysinfo.threads(), ps, fmt, width); 063 renderScripts(sysinfo.scripts(), ps); 064 } 065 public Class<?> clazz() { 066 return SysInfo.class; 067 } 068 public Type type() { 069 return Type.MARKDOWN; 070 } 071 072 private void renderNameRegistrar(List<KV> entries, PrintStream ps, String fmt, int width) { 073 ps.println ("#### NameRegistrar"); 074 ps.print (row(fmt, "id", "component")); 075 ps.print (row(fmt, "-".repeat(width), "---------")); 076 entries.forEach(kv -> { 077 ps.print( 078 row(fmt, kv.key(), kv.value())); 079 }); 080 } 081 082 private void renderThreads(List<KV> entries, PrintStream ps, String fmt, int width) { 083 ps.println ("#### Threads"); 084 ps.print (row(fmt, "thread", "info")); 085 ps.print (row(fmt, "-".repeat(width), "----")); 086 entries.forEach(kv -> { 087 ps.print( 088 row(fmt, kv.key(), kv.value())); 089 }); 090 } 091 092 private void renderScripts(List<ProcessOutput> entries, PrintStream ps) { 093 entries.forEach (processOutput -> { 094 ps.printf ("#### %s%n", processOutput.name()); 095 if (!processOutput.stdout().isEmpty()) { 096 ps.println ("```"); 097 ps.println (processOutput.stdout()); 098 ps.println ("```"); 099 } 100 if (processOutput.stderr() != null) { 101 ps.println ("```"); 102 ps.println (processOutput.stderr()); 103 ps.println ("```"); 104 } 105 }); 106 } 107 108 109 private String row (String fmt, String c1, String c2) { 110 return fmt.formatted(c1, c2); 111 } 112 113 private int maxLength(Map<String, Object> map) { 114 return map.keySet().stream() 115 .map(String::length) 116 .max(Integer::compareTo).orElse(0); 117 } 118 119 private Map<String, Object> extractEntriesToMap(SysInfo record) { 120 return Stream.of(record.getClass().getRecordComponents()) 121 .collect(Collectors.toMap( 122 RecordComponent::getName, 123 component -> { 124 try { 125 Method accessor = component.getAccessor(); 126 return accessor.invoke(record); 127 } catch (IllegalAccessException | InvocationTargetException e) { 128 return "%s:%s".formatted(e.getClass().getSimpleName(), e.getMessage()); 129 } 130 } 131 )); 132 } 133}