From 82022456b2b3265633489fc629d0478e5339d324 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Mon, 26 Aug 2024 11:53:14 +0200 Subject: [PATCH 01/38] Add stack monitor that fails fast if transformer does not yield a sufficient stack number size. --- .../compiler/asmboxpiler/AsmTranspiler.java | 265 +++++++++++++++++- 1 file changed, 256 insertions(+), 9 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index cd8187d0a..f5641db2d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -9,14 +9,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.LdcInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.*; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.Transformer; @@ -151,6 +144,211 @@ public class AsmTranspiler extends Transpiler { + private static final int[] STACK_SIZE_DELTA = { + 0, // nop = 0 (0x0) + 1, // aconst_null = 1 (0x1) + 1, // iconst_m1 = 2 (0x2) + 1, // iconst_0 = 3 (0x3) + 1, // iconst_1 = 4 (0x4) + 1, // iconst_2 = 5 (0x5) + 1, // iconst_3 = 6 (0x6) + 1, // iconst_4 = 7 (0x7) + 1, // iconst_5 = 8 (0x8) + 2, // lconst_0 = 9 (0x9) + 2, // lconst_1 = 10 (0xa) + 1, // fconst_0 = 11 (0xb) + 1, // fconst_1 = 12 (0xc) + 1, // fconst_2 = 13 (0xd) + 2, // dconst_0 = 14 (0xe) + 2, // dconst_1 = 15 (0xf) + 1, // bipush = 16 (0x10) + 1, // sipush = 17 (0x11) + Integer.MIN_VALUE, // ldc = 18 (0x12) + Integer.MIN_VALUE, // ldc_w = 19 (0x13) + Integer.MIN_VALUE, // ldc2_w = 20 (0x14) + 1, // iload = 21 (0x15) + 2, // lload = 22 (0x16) + 1, // fload = 23 (0x17) + 2, // dload = 24 (0x18) + 1, // aload = 25 (0x19) + 1, // iload_0 = 26 (0x1a) + 1, // iload_1 = 27 (0x1b) + 1, // iload_2 = 28 (0x1c) + 1, // iload_3 = 29 (0x1d) + 2, // lload_0 = 30 (0x1e) + 2, // lload_1 = 31 (0x1f) + 2, // lload_2 = 32 (0x20) + 2, // lload_3 = 33 (0x21) + 1, // fload_0 = 34 (0x22) + 1, // fload_1 = 35 (0x23) + 1, // fload_2 = 36 (0x24) + 1, // fload_3 = 37 (0x25) + 2, // dload_0 = 38 (0x26) + 2, // dload_1 = 39 (0x27) + 2, // dload_2 = 40 (0x28) + 2, // dload_3 = 41 (0x29) + 1, // aload_0 = 42 (0x2a) + 1, // aload_1 = 43 (0x2b) + 1, // aload_2 = 44 (0x2c) + 1, // aload_3 = 45 (0x2d) + -1, // iaload = 46 (0x2e) + 0, // laload = 47 (0x2f) + -1, // faload = 48 (0x30) + 0, // daload = 49 (0x31) + -1, // aaload = 50 (0x32) + -1, // baload = 51 (0x33) + -1, // caload = 52 (0x34) + -1, // saload = 53 (0x35) + -1, // istore = 54 (0x36) + -2, // lstore = 55 (0x37) + -1, // fstore = 56 (0x38) + -2, // dstore = 57 (0x39) + -1, // astore = 58 (0x3a) + -1, // istore_0 = 59 (0x3b) + -1, // istore_1 = 60 (0x3c) + -1, // istore_2 = 61 (0x3d) + -1, // istore_3 = 62 (0x3e) + -2, // lstore_0 = 63 (0x3f) + -2, // lstore_1 = 64 (0x40) + -2, // lstore_2 = 65 (0x41) + -2, // lstore_3 = 66 (0x42) + -1, // fstore_0 = 67 (0x43) + -1, // fstore_1 = 68 (0x44) + -1, // fstore_2 = 69 (0x45) + -1, // fstore_3 = 70 (0x46) + -2, // dstore_0 = 71 (0x47) + -2, // dstore_1 = 72 (0x48) + -2, // dstore_2 = 73 (0x49) + -2, // dstore_3 = 74 (0x4a) + -1, // astore_0 = 75 (0x4b) + -1, // astore_1 = 76 (0x4c) + -1, // astore_2 = 77 (0x4d) + -1, // astore_3 = 78 (0x4e) + -3, // iastore = 79 (0x4f) + -4, // lastore = 80 (0x50) + -3, // fastore = 81 (0x51) + -4, // dastore = 82 (0x52) + -3, // aastore = 83 (0x53) + -3, // bastore = 84 (0x54) + -3, // castore = 85 (0x55) + -3, // sastore = 86 (0x56) + -1, // pop = 87 (0x57) + -2, // pop2 = 88 (0x58) + 1, // dup = 89 (0x59) + 1, // dup_x1 = 90 (0x5a) + 1, // dup_x2 = 91 (0x5b) + 2, // dup2 = 92 (0x5c) + 2, // dup2_x1 = 93 (0x5d) + 2, // dup2_x2 = 94 (0x5e) + 0, // swap = 95 (0x5f) + -1, // iadd = 96 (0x60) + -2, // ladd = 97 (0x61) + -1, // fadd = 98 (0x62) + -2, // dadd = 99 (0x63) + -1, // isub = 100 (0x64) + -2, // lsub = 101 (0x65) + -1, // fsub = 102 (0x66) + -2, // dsub = 103 (0x67) + -1, // imul = 104 (0x68) + -2, // lmul = 105 (0x69) + -1, // fmul = 106 (0x6a) + -2, // dmul = 107 (0x6b) + -1, // idiv = 108 (0x6c) + -2, // ldiv = 109 (0x6d) + -1, // fdiv = 110 (0x6e) + -2, // ddiv = 111 (0x6f) + -1, // irem = 112 (0x70) + -2, // lrem = 113 (0x71) + -1, // frem = 114 (0x72) + -2, // drem = 115 (0x73) + 0, // ineg = 116 (0x74) + 0, // lneg = 117 (0x75) + 0, // fneg = 118 (0x76) + 0, // dneg = 119 (0x77) + -1, // ishl = 120 (0x78) + -1, // lshl = 121 (0x79) + -1, // ishr = 122 (0x7a) + -1, // lshr = 123 (0x7b) + -1, // iushr = 124 (0x7c) + -1, // lushr = 125 (0x7d) + -1, // iand = 126 (0x7e) + -2, // land = 127 (0x7f) + -1, // ior = 128 (0x80) + -2, // lor = 129 (0x81) + -1, // ixor = 130 (0x82) + -2, // lxor = 131 (0x83) + 0, // iinc = 132 (0x84) + 1, // i2l = 133 (0x85) + 0, // i2f = 134 (0x86) + 1, // i2d = 135 (0x87) + -1, // l2i = 136 (0x88) + -1, // l2f = 137 (0x89) + 0, // l2d = 138 (0x8a) + 0, // f2i = 139 (0x8b) + 1, // f2l = 140 (0x8c) + 1, // f2d = 141 (0x8d) + -1, // d2i = 142 (0x8e) + 0, // d2l = 143 (0x8f) + -1, // d2f = 144 (0x90) + 0, // i2b = 145 (0x91) + 0, // i2c = 146 (0x92) + 0, // i2s = 147 (0x93) + -3, // lcmp = 148 (0x94) + -1, // fcmpl = 149 (0x95) + -1, // fcmpg = 150 (0x96) + -3, // dcmpl = 151 (0x97) + -3, // dcmpg = 152 (0x98) + -1, // ifeq = 153 (0x99) + -1, // ifne = 154 (0x9a) + -1, // iflt = 155 (0x9b) + -1, // ifge = 156 (0x9c) + -1, // ifgt = 157 (0x9d) + -1, // ifle = 158 (0x9e) + -2, // if_icmpeq = 159 (0x9f) + -2, // if_icmpne = 160 (0xa0) + -2, // if_icmplt = 161 (0xa1) + -2, // if_icmpge = 162 (0xa2) + -2, // if_icmpgt = 163 (0xa3) + -2, // if_icmple = 164 (0xa4) + -2, // if_acmpeq = 165 (0xa5) + -2, // if_acmpne = 166 (0xa6) + 0, // goto = 167 (0xa7) + 1, // jsr = 168 (0xa8) + 0, // ret = 169 (0xa9) + -1, // tableswitch = 170 (0xaa) + -1, // lookupswitch = 171 (0xab) + -1, // ireturn = 172 (0xac) + -2, // lreturn = 173 (0xad) + -1, // freturn = 174 (0xae) + -2, // dreturn = 175 (0xaf) + -1, // areturn = 176 (0xb0) + 0, // return = 177 (0xb1) + Integer.MIN_VALUE, // getstatic = 178 (0xb2) + Integer.MIN_VALUE, // putstatic = 179 (0xb3) + Integer.MIN_VALUE, // getfield = 180 (0xb4) + Integer.MIN_VALUE, // putfield = 181 (0xb5) + Integer.MIN_VALUE, // invokevirtual = 182 (0xb6) + Integer.MIN_VALUE, // invokespecial = 183 (0xb7) + Integer.MIN_VALUE, // invokestatic = 184 (0xb8) + Integer.MIN_VALUE, // invokeinterface = 185 (0xb9) + Integer.MIN_VALUE, // invokedynamic = 186 (0xba) + 1, // new = 187 (0xbb) + 0, // newarray = 188 (0xbc) + 0, // anewarray = 189 (0xbd) + 0, // arraylength = 190 (0xbe) + -1, // athrow = 191 (0xbf) + 0, // checkcast = 192 (0xc0) + 0, // instanceof = 193 (0xc1) + -1, // monitorenter = 194 (0xc2) + -1, // monitorexit = 195 (0xc3) + Integer.MIN_VALUE, // wide = 196 (0xc4) + Integer.MIN_VALUE, // multianewarray = 197 (0xc5) + -1, // ifnull = 198 (0xc6) + -1, // ifnonnull = 199 (0xc7) + Integer.MIN_VALUE, // goto_w = 200 (0xc8) + Integer.MIN_VALUE // jsr_w = 201 (0xc9) + }; + private static HashMap, Transformer> registry = new HashMap<>(); private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava"; @@ -765,7 +963,56 @@ public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnValueContext ) { Transformer transformer = registry.get( node.getClass() ); if ( transformer != null ) { - return transformer.transform( node, context, returnValueContext ); + List nodes = transformer.transform( node, context, returnValueContext ); + if (ASMBoxpiler.DEBUG) { + int delta = 0; + for (AbstractInsnNode value : nodes) { + if (value instanceof FieldInsnNode fieldInsnNode) { + Type type = Type.getType(fieldInsnNode.desc); + delta += switch (fieldInsnNode.getOpcode()) { + case Opcodes.GETSTATIC -> type.getSize(); + case Opcodes.PUTSTATIC -> -type.getSize(); + case Opcodes.GETFIELD -> type.getSize() - 1; + case Opcodes.PUTFIELD -> -type.getSize() - 1; + default -> throw new IllegalStateException(); + }; + } else if (value instanceof MethodInsnNode methodInsnNode) { + Type type = Type.getMethodType(methodInsnNode.desc); + for (Type argument : type.getArgumentTypes()) { + delta -= argument.getSize(); + } + delta += type.getReturnType().getSize(); + delta += switch (methodInsnNode.getOpcode()) { + case Opcodes.INVOKESTATIC -> 0; + case Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL -> -1; + default -> throw new IllegalStateException(); + }; + } else if (value instanceof InvokeDynamicInsnNode invokeDynamicInsnNode) { + Type type = Type.getMethodType(invokeDynamicInsnNode.desc); + for (Type argument : type.getArgumentTypes()) { + delta -= argument.getSize(); + } + delta += type.getReturnType().getSize(); + } else if (value instanceof LdcInsnNode ldcInsnNode) { + delta += ldcInsnNode.cst instanceof Double || ldcInsnNode.cst instanceof Long ? 2 : 1; + } else if (value instanceof MultiANewArrayInsnNode multiANewArrayInsnNode) { + delta -= multiANewArrayInsnNode.dims + 1; + } else { + if (STACK_SIZE_DELTA[value.getOpcode()] == Integer.MIN_VALUE) { + throw new IllegalStateException(); + } + delta += STACK_SIZE_DELTA[value.getOpcode()]; + } + } + int expectation = switch (returnValueContext) { + case EMPTY, EMPTY_UNLESS_JUMPING -> 0; + case VALUE, VALUE_OR_NULL -> 1; + }; + if (expectation != delta) { + throw new IllegalStateException( node.getClass() + " with " + returnValueContext + " yielded a stack delta of " + delta); + } + } + return nodes; } throw new IllegalStateException( "unsupported: " + node.getClass().getSimpleName() + " : " + node.getSourceText() ); } From e6cdd5c0c1e8c2928955f4f083d28eac5a64be62 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Mon, 26 Aug 2024 12:08:43 +0200 Subject: [PATCH 02/38] Skip nodes that do not represent an opcode. --- .../ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index f5641db2d..8e2382520 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -967,7 +967,9 @@ public List transform( BoxNode node, TransformerContext contex if (ASMBoxpiler.DEBUG) { int delta = 0; for (AbstractInsnNode value : nodes) { - if (value instanceof FieldInsnNode fieldInsnNode) { + if (value.getOpcode() == -1) { + continue; + } else if (value instanceof FieldInsnNode fieldInsnNode) { Type type = Type.getType(fieldInsnNode.desc); delta += switch (fieldInsnNode.getOpcode()) { case Opcodes.GETSTATIC -> type.getSize(); From 6b0257f966c6d30272e950cb4cc57eed752ab50e Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Wed, 18 Sep 2024 08:41:45 +0200 Subject: [PATCH 03/38] Fix formatting. --- .../compiler/asmboxpiler/ASMBoxpiler.java | 6 +- .../compiler/asmboxpiler/AsmTranspiler.java | 456 +++++++++--------- 2 files changed, 231 insertions(+), 231 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java index fb6f8733c..a8c5fd6d6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java @@ -23,7 +23,7 @@ public class ASMBoxpiler extends Boxpiler { - public static final boolean DEBUG = Boolean.getBoolean("asmboxpiler.debug"); + public static final boolean DEBUG = Boolean.getBoolean( "asmboxpiler.debug" ); /** * -------------------------------------------------------------------------- @@ -34,7 +34,7 @@ public class ASMBoxpiler extends Boxpiler { /** * Singleton instance */ - private static ASMBoxpiler instance; + private static ASMBoxpiler instance; /** * -------------------------------------------------------------------------- @@ -100,7 +100,7 @@ private void doWriteClassInfo( BoxNode node, ClassInfo classInfo ) { node.accept( new QueryEscapeSingleQuoteVisitor() ); doCompileClassInfo( transpiler( classInfo ), classInfo, node, ( fqn, classNode ) -> { ClassWriter classWriter = new ClassWriter( ClassWriter.COMPUTE_FRAMES ); - if (DEBUG) { + if ( DEBUG ) { classNode.accept( new CheckClassAdapter( new TraceClassVisitor( classWriter, new PrintWriter( System.out ) ) ) ); } else { classNode.accept( classWriter ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 8e2382520..300bc68ec 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -144,209 +144,209 @@ public class AsmTranspiler extends Transpiler { - private static final int[] STACK_SIZE_DELTA = { - 0, // nop = 0 (0x0) - 1, // aconst_null = 1 (0x1) - 1, // iconst_m1 = 2 (0x2) - 1, // iconst_0 = 3 (0x3) - 1, // iconst_1 = 4 (0x4) - 1, // iconst_2 = 5 (0x5) - 1, // iconst_3 = 6 (0x6) - 1, // iconst_4 = 7 (0x7) - 1, // iconst_5 = 8 (0x8) - 2, // lconst_0 = 9 (0x9) - 2, // lconst_1 = 10 (0xa) - 1, // fconst_0 = 11 (0xb) - 1, // fconst_1 = 12 (0xc) - 1, // fconst_2 = 13 (0xd) - 2, // dconst_0 = 14 (0xe) - 2, // dconst_1 = 15 (0xf) - 1, // bipush = 16 (0x10) - 1, // sipush = 17 (0x11) - Integer.MIN_VALUE, // ldc = 18 (0x12) - Integer.MIN_VALUE, // ldc_w = 19 (0x13) - Integer.MIN_VALUE, // ldc2_w = 20 (0x14) - 1, // iload = 21 (0x15) - 2, // lload = 22 (0x16) - 1, // fload = 23 (0x17) - 2, // dload = 24 (0x18) - 1, // aload = 25 (0x19) - 1, // iload_0 = 26 (0x1a) - 1, // iload_1 = 27 (0x1b) - 1, // iload_2 = 28 (0x1c) - 1, // iload_3 = 29 (0x1d) - 2, // lload_0 = 30 (0x1e) - 2, // lload_1 = 31 (0x1f) - 2, // lload_2 = 32 (0x20) - 2, // lload_3 = 33 (0x21) - 1, // fload_0 = 34 (0x22) - 1, // fload_1 = 35 (0x23) - 1, // fload_2 = 36 (0x24) - 1, // fload_3 = 37 (0x25) - 2, // dload_0 = 38 (0x26) - 2, // dload_1 = 39 (0x27) - 2, // dload_2 = 40 (0x28) - 2, // dload_3 = 41 (0x29) - 1, // aload_0 = 42 (0x2a) - 1, // aload_1 = 43 (0x2b) - 1, // aload_2 = 44 (0x2c) - 1, // aload_3 = 45 (0x2d) - -1, // iaload = 46 (0x2e) - 0, // laload = 47 (0x2f) - -1, // faload = 48 (0x30) - 0, // daload = 49 (0x31) - -1, // aaload = 50 (0x32) - -1, // baload = 51 (0x33) - -1, // caload = 52 (0x34) - -1, // saload = 53 (0x35) - -1, // istore = 54 (0x36) - -2, // lstore = 55 (0x37) - -1, // fstore = 56 (0x38) - -2, // dstore = 57 (0x39) - -1, // astore = 58 (0x3a) - -1, // istore_0 = 59 (0x3b) - -1, // istore_1 = 60 (0x3c) - -1, // istore_2 = 61 (0x3d) - -1, // istore_3 = 62 (0x3e) - -2, // lstore_0 = 63 (0x3f) - -2, // lstore_1 = 64 (0x40) - -2, // lstore_2 = 65 (0x41) - -2, // lstore_3 = 66 (0x42) - -1, // fstore_0 = 67 (0x43) - -1, // fstore_1 = 68 (0x44) - -1, // fstore_2 = 69 (0x45) - -1, // fstore_3 = 70 (0x46) - -2, // dstore_0 = 71 (0x47) - -2, // dstore_1 = 72 (0x48) - -2, // dstore_2 = 73 (0x49) - -2, // dstore_3 = 74 (0x4a) - -1, // astore_0 = 75 (0x4b) - -1, // astore_1 = 76 (0x4c) - -1, // astore_2 = 77 (0x4d) - -1, // astore_3 = 78 (0x4e) - -3, // iastore = 79 (0x4f) - -4, // lastore = 80 (0x50) - -3, // fastore = 81 (0x51) - -4, // dastore = 82 (0x52) - -3, // aastore = 83 (0x53) - -3, // bastore = 84 (0x54) - -3, // castore = 85 (0x55) - -3, // sastore = 86 (0x56) - -1, // pop = 87 (0x57) - -2, // pop2 = 88 (0x58) - 1, // dup = 89 (0x59) - 1, // dup_x1 = 90 (0x5a) - 1, // dup_x2 = 91 (0x5b) - 2, // dup2 = 92 (0x5c) - 2, // dup2_x1 = 93 (0x5d) - 2, // dup2_x2 = 94 (0x5e) - 0, // swap = 95 (0x5f) - -1, // iadd = 96 (0x60) - -2, // ladd = 97 (0x61) - -1, // fadd = 98 (0x62) - -2, // dadd = 99 (0x63) - -1, // isub = 100 (0x64) - -2, // lsub = 101 (0x65) - -1, // fsub = 102 (0x66) - -2, // dsub = 103 (0x67) - -1, // imul = 104 (0x68) - -2, // lmul = 105 (0x69) - -1, // fmul = 106 (0x6a) - -2, // dmul = 107 (0x6b) - -1, // idiv = 108 (0x6c) - -2, // ldiv = 109 (0x6d) - -1, // fdiv = 110 (0x6e) - -2, // ddiv = 111 (0x6f) - -1, // irem = 112 (0x70) - -2, // lrem = 113 (0x71) - -1, // frem = 114 (0x72) - -2, // drem = 115 (0x73) - 0, // ineg = 116 (0x74) - 0, // lneg = 117 (0x75) - 0, // fneg = 118 (0x76) - 0, // dneg = 119 (0x77) - -1, // ishl = 120 (0x78) - -1, // lshl = 121 (0x79) - -1, // ishr = 122 (0x7a) - -1, // lshr = 123 (0x7b) - -1, // iushr = 124 (0x7c) - -1, // lushr = 125 (0x7d) - -1, // iand = 126 (0x7e) - -2, // land = 127 (0x7f) - -1, // ior = 128 (0x80) - -2, // lor = 129 (0x81) - -1, // ixor = 130 (0x82) - -2, // lxor = 131 (0x83) - 0, // iinc = 132 (0x84) - 1, // i2l = 133 (0x85) - 0, // i2f = 134 (0x86) - 1, // i2d = 135 (0x87) - -1, // l2i = 136 (0x88) - -1, // l2f = 137 (0x89) - 0, // l2d = 138 (0x8a) - 0, // f2i = 139 (0x8b) - 1, // f2l = 140 (0x8c) - 1, // f2d = 141 (0x8d) - -1, // d2i = 142 (0x8e) - 0, // d2l = 143 (0x8f) - -1, // d2f = 144 (0x90) - 0, // i2b = 145 (0x91) - 0, // i2c = 146 (0x92) - 0, // i2s = 147 (0x93) - -3, // lcmp = 148 (0x94) - -1, // fcmpl = 149 (0x95) - -1, // fcmpg = 150 (0x96) - -3, // dcmpl = 151 (0x97) - -3, // dcmpg = 152 (0x98) - -1, // ifeq = 153 (0x99) - -1, // ifne = 154 (0x9a) - -1, // iflt = 155 (0x9b) - -1, // ifge = 156 (0x9c) - -1, // ifgt = 157 (0x9d) - -1, // ifle = 158 (0x9e) - -2, // if_icmpeq = 159 (0x9f) - -2, // if_icmpne = 160 (0xa0) - -2, // if_icmplt = 161 (0xa1) - -2, // if_icmpge = 162 (0xa2) - -2, // if_icmpgt = 163 (0xa3) - -2, // if_icmple = 164 (0xa4) - -2, // if_acmpeq = 165 (0xa5) - -2, // if_acmpne = 166 (0xa6) - 0, // goto = 167 (0xa7) - 1, // jsr = 168 (0xa8) - 0, // ret = 169 (0xa9) - -1, // tableswitch = 170 (0xaa) - -1, // lookupswitch = 171 (0xab) - -1, // ireturn = 172 (0xac) - -2, // lreturn = 173 (0xad) - -1, // freturn = 174 (0xae) - -2, // dreturn = 175 (0xaf) - -1, // areturn = 176 (0xb0) - 0, // return = 177 (0xb1) - Integer.MIN_VALUE, // getstatic = 178 (0xb2) - Integer.MIN_VALUE, // putstatic = 179 (0xb3) - Integer.MIN_VALUE, // getfield = 180 (0xb4) - Integer.MIN_VALUE, // putfield = 181 (0xb5) - Integer.MIN_VALUE, // invokevirtual = 182 (0xb6) - Integer.MIN_VALUE, // invokespecial = 183 (0xb7) - Integer.MIN_VALUE, // invokestatic = 184 (0xb8) - Integer.MIN_VALUE, // invokeinterface = 185 (0xb9) - Integer.MIN_VALUE, // invokedynamic = 186 (0xba) - 1, // new = 187 (0xbb) - 0, // newarray = 188 (0xbc) - 0, // anewarray = 189 (0xbd) - 0, // arraylength = 190 (0xbe) - -1, // athrow = 191 (0xbf) - 0, // checkcast = 192 (0xc0) - 0, // instanceof = 193 (0xc1) - -1, // monitorenter = 194 (0xc2) - -1, // monitorexit = 195 (0xc3) - Integer.MIN_VALUE, // wide = 196 (0xc4) - Integer.MIN_VALUE, // multianewarray = 197 (0xc5) - -1, // ifnull = 198 (0xc6) - -1, // ifnonnull = 199 (0xc7) - Integer.MIN_VALUE, // goto_w = 200 (0xc8) - Integer.MIN_VALUE // jsr_w = 201 (0xc9) + private static final int[] STACK_SIZE_DELTA = { + 0, // nop = 0 (0x0) + 1, // aconst_null = 1 (0x1) + 1, // iconst_m1 = 2 (0x2) + 1, // iconst_0 = 3 (0x3) + 1, // iconst_1 = 4 (0x4) + 1, // iconst_2 = 5 (0x5) + 1, // iconst_3 = 6 (0x6) + 1, // iconst_4 = 7 (0x7) + 1, // iconst_5 = 8 (0x8) + 2, // lconst_0 = 9 (0x9) + 2, // lconst_1 = 10 (0xa) + 1, // fconst_0 = 11 (0xb) + 1, // fconst_1 = 12 (0xc) + 1, // fconst_2 = 13 (0xd) + 2, // dconst_0 = 14 (0xe) + 2, // dconst_1 = 15 (0xf) + 1, // bipush = 16 (0x10) + 1, // sipush = 17 (0x11) + Integer.MIN_VALUE, // ldc = 18 (0x12) + Integer.MIN_VALUE, // ldc_w = 19 (0x13) + Integer.MIN_VALUE, // ldc2_w = 20 (0x14) + 1, // iload = 21 (0x15) + 2, // lload = 22 (0x16) + 1, // fload = 23 (0x17) + 2, // dload = 24 (0x18) + 1, // aload = 25 (0x19) + 1, // iload_0 = 26 (0x1a) + 1, // iload_1 = 27 (0x1b) + 1, // iload_2 = 28 (0x1c) + 1, // iload_3 = 29 (0x1d) + 2, // lload_0 = 30 (0x1e) + 2, // lload_1 = 31 (0x1f) + 2, // lload_2 = 32 (0x20) + 2, // lload_3 = 33 (0x21) + 1, // fload_0 = 34 (0x22) + 1, // fload_1 = 35 (0x23) + 1, // fload_2 = 36 (0x24) + 1, // fload_3 = 37 (0x25) + 2, // dload_0 = 38 (0x26) + 2, // dload_1 = 39 (0x27) + 2, // dload_2 = 40 (0x28) + 2, // dload_3 = 41 (0x29) + 1, // aload_0 = 42 (0x2a) + 1, // aload_1 = 43 (0x2b) + 1, // aload_2 = 44 (0x2c) + 1, // aload_3 = 45 (0x2d) + -1, // iaload = 46 (0x2e) + 0, // laload = 47 (0x2f) + -1, // faload = 48 (0x30) + 0, // daload = 49 (0x31) + -1, // aaload = 50 (0x32) + -1, // baload = 51 (0x33) + -1, // caload = 52 (0x34) + -1, // saload = 53 (0x35) + -1, // istore = 54 (0x36) + -2, // lstore = 55 (0x37) + -1, // fstore = 56 (0x38) + -2, // dstore = 57 (0x39) + -1, // astore = 58 (0x3a) + -1, // istore_0 = 59 (0x3b) + -1, // istore_1 = 60 (0x3c) + -1, // istore_2 = 61 (0x3d) + -1, // istore_3 = 62 (0x3e) + -2, // lstore_0 = 63 (0x3f) + -2, // lstore_1 = 64 (0x40) + -2, // lstore_2 = 65 (0x41) + -2, // lstore_3 = 66 (0x42) + -1, // fstore_0 = 67 (0x43) + -1, // fstore_1 = 68 (0x44) + -1, // fstore_2 = 69 (0x45) + -1, // fstore_3 = 70 (0x46) + -2, // dstore_0 = 71 (0x47) + -2, // dstore_1 = 72 (0x48) + -2, // dstore_2 = 73 (0x49) + -2, // dstore_3 = 74 (0x4a) + -1, // astore_0 = 75 (0x4b) + -1, // astore_1 = 76 (0x4c) + -1, // astore_2 = 77 (0x4d) + -1, // astore_3 = 78 (0x4e) + -3, // iastore = 79 (0x4f) + -4, // lastore = 80 (0x50) + -3, // fastore = 81 (0x51) + -4, // dastore = 82 (0x52) + -3, // aastore = 83 (0x53) + -3, // bastore = 84 (0x54) + -3, // castore = 85 (0x55) + -3, // sastore = 86 (0x56) + -1, // pop = 87 (0x57) + -2, // pop2 = 88 (0x58) + 1, // dup = 89 (0x59) + 1, // dup_x1 = 90 (0x5a) + 1, // dup_x2 = 91 (0x5b) + 2, // dup2 = 92 (0x5c) + 2, // dup2_x1 = 93 (0x5d) + 2, // dup2_x2 = 94 (0x5e) + 0, // swap = 95 (0x5f) + -1, // iadd = 96 (0x60) + -2, // ladd = 97 (0x61) + -1, // fadd = 98 (0x62) + -2, // dadd = 99 (0x63) + -1, // isub = 100 (0x64) + -2, // lsub = 101 (0x65) + -1, // fsub = 102 (0x66) + -2, // dsub = 103 (0x67) + -1, // imul = 104 (0x68) + -2, // lmul = 105 (0x69) + -1, // fmul = 106 (0x6a) + -2, // dmul = 107 (0x6b) + -1, // idiv = 108 (0x6c) + -2, // ldiv = 109 (0x6d) + -1, // fdiv = 110 (0x6e) + -2, // ddiv = 111 (0x6f) + -1, // irem = 112 (0x70) + -2, // lrem = 113 (0x71) + -1, // frem = 114 (0x72) + -2, // drem = 115 (0x73) + 0, // ineg = 116 (0x74) + 0, // lneg = 117 (0x75) + 0, // fneg = 118 (0x76) + 0, // dneg = 119 (0x77) + -1, // ishl = 120 (0x78) + -1, // lshl = 121 (0x79) + -1, // ishr = 122 (0x7a) + -1, // lshr = 123 (0x7b) + -1, // iushr = 124 (0x7c) + -1, // lushr = 125 (0x7d) + -1, // iand = 126 (0x7e) + -2, // land = 127 (0x7f) + -1, // ior = 128 (0x80) + -2, // lor = 129 (0x81) + -1, // ixor = 130 (0x82) + -2, // lxor = 131 (0x83) + 0, // iinc = 132 (0x84) + 1, // i2l = 133 (0x85) + 0, // i2f = 134 (0x86) + 1, // i2d = 135 (0x87) + -1, // l2i = 136 (0x88) + -1, // l2f = 137 (0x89) + 0, // l2d = 138 (0x8a) + 0, // f2i = 139 (0x8b) + 1, // f2l = 140 (0x8c) + 1, // f2d = 141 (0x8d) + -1, // d2i = 142 (0x8e) + 0, // d2l = 143 (0x8f) + -1, // d2f = 144 (0x90) + 0, // i2b = 145 (0x91) + 0, // i2c = 146 (0x92) + 0, // i2s = 147 (0x93) + -3, // lcmp = 148 (0x94) + -1, // fcmpl = 149 (0x95) + -1, // fcmpg = 150 (0x96) + -3, // dcmpl = 151 (0x97) + -3, // dcmpg = 152 (0x98) + -1, // ifeq = 153 (0x99) + -1, // ifne = 154 (0x9a) + -1, // iflt = 155 (0x9b) + -1, // ifge = 156 (0x9c) + -1, // ifgt = 157 (0x9d) + -1, // ifle = 158 (0x9e) + -2, // if_icmpeq = 159 (0x9f) + -2, // if_icmpne = 160 (0xa0) + -2, // if_icmplt = 161 (0xa1) + -2, // if_icmpge = 162 (0xa2) + -2, // if_icmpgt = 163 (0xa3) + -2, // if_icmple = 164 (0xa4) + -2, // if_acmpeq = 165 (0xa5) + -2, // if_acmpne = 166 (0xa6) + 0, // goto = 167 (0xa7) + 1, // jsr = 168 (0xa8) + 0, // ret = 169 (0xa9) + -1, // tableswitch = 170 (0xaa) + -1, // lookupswitch = 171 (0xab) + -1, // ireturn = 172 (0xac) + -2, // lreturn = 173 (0xad) + -1, // freturn = 174 (0xae) + -2, // dreturn = 175 (0xaf) + -1, // areturn = 176 (0xb0) + 0, // return = 177 (0xb1) + Integer.MIN_VALUE, // getstatic = 178 (0xb2) + Integer.MIN_VALUE, // putstatic = 179 (0xb3) + Integer.MIN_VALUE, // getfield = 180 (0xb4) + Integer.MIN_VALUE, // putfield = 181 (0xb5) + Integer.MIN_VALUE, // invokevirtual = 182 (0xb6) + Integer.MIN_VALUE, // invokespecial = 183 (0xb7) + Integer.MIN_VALUE, // invokestatic = 184 (0xb8) + Integer.MIN_VALUE, // invokeinterface = 185 (0xb9) + Integer.MIN_VALUE, // invokedynamic = 186 (0xba) + 1, // new = 187 (0xbb) + 0, // newarray = 188 (0xbc) + 0, // anewarray = 189 (0xbd) + 0, // arraylength = 190 (0xbe) + -1, // athrow = 191 (0xbf) + 0, // checkcast = 192 (0xc0) + 0, // instanceof = 193 (0xc1) + -1, // monitorenter = 194 (0xc2) + -1, // monitorexit = 195 (0xc3) + Integer.MIN_VALUE, // wide = 196 (0xc4) + Integer.MIN_VALUE, // multianewarray = 197 (0xc5) + -1, // ifnull = 198 (0xc6) + -1, // ifnonnull = 199 (0xc7) + Integer.MIN_VALUE, // goto_w = 200 (0xc8) + Integer.MIN_VALUE // jsr_w = 201 (0xc9) }; private static HashMap, Transformer> registry = new HashMap<>(); @@ -964,54 +964,54 @@ public List transform( BoxNode node, TransformerContext contex Transformer transformer = registry.get( node.getClass() ); if ( transformer != null ) { List nodes = transformer.transform( node, context, returnValueContext ); - if (ASMBoxpiler.DEBUG) { + if ( ASMBoxpiler.DEBUG ) { int delta = 0; - for (AbstractInsnNode value : nodes) { - if (value.getOpcode() == -1) { + for ( AbstractInsnNode value : nodes ) { + if ( value.getOpcode() == -1 ) { continue; - } else if (value instanceof FieldInsnNode fieldInsnNode) { - Type type = Type.getType(fieldInsnNode.desc); - delta += switch (fieldInsnNode.getOpcode()) { + } else if ( value instanceof FieldInsnNode fieldInsnNode ) { + Type type = Type.getType( fieldInsnNode.desc ); + delta += switch ( fieldInsnNode.getOpcode() ) { case Opcodes.GETSTATIC -> type.getSize(); case Opcodes.PUTSTATIC -> -type.getSize(); case Opcodes.GETFIELD -> type.getSize() - 1; case Opcodes.PUTFIELD -> -type.getSize() - 1; default -> throw new IllegalStateException(); }; - } else if (value instanceof MethodInsnNode methodInsnNode) { - Type type = Type.getMethodType(methodInsnNode.desc); - for (Type argument : type.getArgumentTypes()) { + } else if ( value instanceof MethodInsnNode methodInsnNode ) { + Type type = Type.getMethodType( methodInsnNode.desc ); + for ( Type argument : type.getArgumentTypes() ) { delta -= argument.getSize(); } - delta += type.getReturnType().getSize(); - delta += switch (methodInsnNode.getOpcode()) { - case Opcodes.INVOKESTATIC -> 0; - case Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL -> -1; - default -> throw new IllegalStateException(); - }; - } else if (value instanceof InvokeDynamicInsnNode invokeDynamicInsnNode) { - Type type = Type.getMethodType(invokeDynamicInsnNode.desc); - for (Type argument : type.getArgumentTypes()) { + delta += type.getReturnType().getSize(); + delta += switch ( methodInsnNode.getOpcode() ) { + case Opcodes.INVOKESTATIC -> 0; + case Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL -> -1; + default -> throw new IllegalStateException(); + }; + } else if ( value instanceof InvokeDynamicInsnNode invokeDynamicInsnNode ) { + Type type = Type.getMethodType( invokeDynamicInsnNode.desc ); + for ( Type argument : type.getArgumentTypes() ) { delta -= argument.getSize(); } delta += type.getReturnType().getSize(); - } else if (value instanceof LdcInsnNode ldcInsnNode) { + } else if ( value instanceof LdcInsnNode ldcInsnNode ) { delta += ldcInsnNode.cst instanceof Double || ldcInsnNode.cst instanceof Long ? 2 : 1; - } else if (value instanceof MultiANewArrayInsnNode multiANewArrayInsnNode) { + } else if ( value instanceof MultiANewArrayInsnNode multiANewArrayInsnNode ) { delta -= multiANewArrayInsnNode.dims + 1; } else { - if (STACK_SIZE_DELTA[value.getOpcode()] == Integer.MIN_VALUE) { + if ( STACK_SIZE_DELTA[ value.getOpcode() ] == Integer.MIN_VALUE ) { throw new IllegalStateException(); } - delta += STACK_SIZE_DELTA[value.getOpcode()]; + delta += STACK_SIZE_DELTA[ value.getOpcode() ]; } } - int expectation = switch (returnValueContext) { + int expectation = switch ( returnValueContext ) { case EMPTY, EMPTY_UNLESS_JUMPING -> 0; case VALUE, VALUE_OR_NULL -> 1; }; - if (expectation != delta) { - throw new IllegalStateException( node.getClass() + " with " + returnValueContext + " yielded a stack delta of " + delta); + if ( expectation != delta ) { + throw new IllegalStateException( node.getClass() + " with " + returnValueContext + " yielded a stack delta of " + delta ); } } return nodes; From 384e10c2b960e4d1a6f6dadfea5f5e0dfc4a4735 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Tue, 19 Nov 2024 22:37:02 +0100 Subject: [PATCH 04/38] Rebase to allow for debugging. --- .../java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 21b968360..5ecc7f8c3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -13,8 +13,10 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.InvokeDynamicInsnNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MultiANewArrayInsnNode; import org.objectweb.asm.tree.TypeInsnNode; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; From 19291689b36430fa8cce831847d9691f600c39d7 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Sat, 23 Nov 2024 12:24:08 +0000 Subject: [PATCH 05/38] Version bump --- changelog.md | 6 +++++- gradle.properties | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index b8f65abdd..7e81d0377 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.0-beta23] - 2024-11-23 + ## [1.0.0-beta22] - 2024-11-15 ## [1.0.0-beta21] - 2024-11-01 @@ -57,7 +59,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0-beta1] - 2024-06-14 -[Unreleased]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta22...HEAD +[Unreleased]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta23...HEAD + +[1.0.0-beta23]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta22...v1.0.0-beta23 [1.0.0-beta22]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta21...v1.0.0-beta22 diff --git a/gradle.properties b/gradle.properties index f546e2e64..ae3557676 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -#Fri Nov 15 22:11:50 UTC 2024 +#Sat Nov 23 12:24:04 UTC 2024 antlrVersion=4.13.1 jdkVersion=21 -version=1.0.0-beta23 +version=1.0.0-beta24 From 8345ef18d9f00a866e6e60a61c7c887b28007c06 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 12:10:15 +0100 Subject: [PATCH 06/38] BL-785 #resolve Update logging format to use ISO8601 --- .../ortus/boxlang/runtime/logging/LoggingConfigurator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java index 81f0aefa1..870974287 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java @@ -49,7 +49,7 @@ public class LoggingConfigurator extends LoggerContextAwareBase implements Confi * * @see https://logback.qos.ch/manual/layouts.html#conversionWord */ - public static final String LOG_FORMAT = "%date %logger{0} [%level] %kvp %message%n"; + public static final String LOG_FORMAT = "%date{STRICT} %logger{0} [%level] %kvp %message%n"; /** * The name of the context for the runtime @@ -82,7 +82,6 @@ public ExecutionStatus configure( LoggerContext loggerContext ) { var encoder = new PatternLayoutEncoder(); encoder.setContext( loggerContext ); encoder.setPattern( LOG_FORMAT ); - encoder.setImmediateFlush( true ); encoder.setOutputPatternAsHeader( true ); encoder.start(); From 168077d4f9e51877fbb58d367728a3d30ae54445 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 12:21:01 +0100 Subject: [PATCH 07/38] BL-785 #resolve Update logging format to use ISO8601 and threading standards --- .../java/ortus/boxlang/runtime/logging/LoggingConfigurator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java index 870974287..e77d197ef 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java @@ -49,7 +49,7 @@ public class LoggingConfigurator extends LoggerContextAwareBase implements Confi * * @see https://logback.qos.ch/manual/layouts.html#conversionWord */ - public static final String LOG_FORMAT = "%date{STRICT} %logger{0} [%level] %kvp %message%n"; + public static final String LOG_FORMAT = "[%date{STRICT}] [%highlight(%-5level)] [%thread] [%logger{0}] %kvp %message%n"; /** * The name of the context for the runtime From ee0b22cb799347d3911b3d8470d20ba4a3966129 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 12:22:08 +0100 Subject: [PATCH 08/38] removing logging verbosity --- .../java/ortus/boxlang/runtime/logging/LoggingConfigurator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java index e77d197ef..f0b2e50cf 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java @@ -82,7 +82,6 @@ public ExecutionStatus configure( LoggerContext loggerContext ) { var encoder = new PatternLayoutEncoder(); encoder.setContext( loggerContext ); encoder.setPattern( LOG_FORMAT ); - encoder.setOutputPatternAsHeader( true ); encoder.start(); // Base log level depending on debug mode From 71154279bf9761693e5cb241d792b14c5ae0969e Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 14:27:28 +0100 Subject: [PATCH 09/38] updated all tests to new standards to avoid thread collisions --- .../boxlang/runtime/interceptors/Logging.java | 2 -- .../bifs/global/system/WriteLogTest.java | 33 +++++++++---------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java index be1ed0c00..b9907238d 100644 --- a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java +++ b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java @@ -71,8 +71,6 @@ public Logging( BoxRuntime instance ) { * The log key is a shortcut to a specific log file. Available log files are: Application, Scheduler, etc. * Which is dumb and should be moved to the CFML compatibility module. Leaving until we move it. * - * TODO: Encapsulate all logic to the service for reuse - * * @param data The data to be passed to the interceptor * * @throws IIllegalArgumentException If the log level is not valid diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java index 52c46a5ae..f0be8ae34 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java @@ -18,6 +18,7 @@ package ortus.boxlang.runtime.bifs.global.system; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertTrue; import java.io.PrintStream; @@ -51,6 +52,7 @@ public class WriteLogTest { static String logFilePath; static ByteArrayOutputStream outContent; static PrintStream originalOut = System.out; + static String defaultLogFilePath; @BeforeAll public static void setUp() { @@ -58,7 +60,12 @@ public static void setUp() { logsDirectory = instance.getConfiguration().logsDirectory; outContent = new ByteArrayOutputStream(); System.setOut( new PrintStream( outContent ) ); - logFilePath = Paths.get( logsDirectory, "/writelog.log" ).normalize().toString(); + logFilePath = Paths.get( logsDirectory, "/writelog.log" ).normalize().toString(); + defaultLogFilePath = Paths.get( logsDirectory, "/boxruntime.log" ).normalize().toString(); + + if ( FileSystemUtil.exists( defaultLogFilePath ) ) { + FileSystemUtil.deleteFile( defaultLogFilePath ); + } } @AfterAll @@ -87,7 +94,8 @@ public void testPrint() { context ); // Assert we got here - assertTrue( StringUtils.contains( outContent.toString(), "Hello Logger!" ) ); + assertThat( FileSystemUtil.exists( defaultLogFilePath ) ).isTrue(); + assertThat( outContent.toString() ).contains( "Hello Logger!" ); } @DisplayName( "It can write a default with a compat log argument" ) @@ -99,43 +107,34 @@ public void testCategory() { """, context ); // Assert we got here - assertTrue( StringUtils.contains( outContent.toString(), "Hola Logger!" ) ); + assertThat( StringUtils.contains( outContent.toString(), "Hola Logger!" ) ).isTrue(); } @DisplayName( "It can write a default with a custom log file" ) @Test public void testCustomFile() { - String logFilePath = Paths.get( logsDirectory, "/foo.log" ).normalize().toString(); instance.executeSource( """ - writeLog( text="Custom Logger!", file="foo.log" ) + writeLog( text="Custom Logger!", file="writelog.log" ) """, context ); assertTrue( FileSystemUtil.exists( logFilePath ) ); String fileContent = StringCaster.cast( FileSystemUtil.read( logFilePath ) ); assertTrue( StringUtils.contains( fileContent, "Custom Logger!" ) ); - } - @DisplayName( "It can write a default with a custom log file" ) + @DisplayName( "It can write a default with a custom log file and type" ) @Test public void testCustomFileAndLevel() { - String logFilePath = Paths.get( logsDirectory, "/foo.log" ).normalize().toString(); instance.executeSource( """ - writeLog( text="Hello Error Logger!", file="foo.log", type="Error" ); + writeLog( text="Hello Error Logger!", file="writelog.log", type="Error" ); """, context ); - instance.executeSource( - """ - writeLog( text="Hello Root Logger!" ); - """, - context ); - assertTrue( FileSystemUtil.exists( logFilePath ) ); String fileContent = StringCaster.cast( FileSystemUtil.read( logFilePath ) ); - assertTrue( StringUtils.contains( fileContent, "[ERROR]" ) ); - assertTrue( StringUtils.contains( fileContent, "Hello Error Logger!" ) ); + assertThat( fileContent ).contains( "ERROR" ); + assertThat( fileContent ).contains( "Hello Error Logger!" ); } From d0ba0900486de1f1905a6740cf0b13909f87c371 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 16:37:15 +0100 Subject: [PATCH 10/38] more refactoring into the logging service --- .../boxlang/runtime/interceptors/Logging.java | 131 ++------------ .../runtime/logging/LoggingConfigurator.java | 23 +-- .../runtime/logging/LoggingService.java | 163 +++++++++++++++++- 3 files changed, 182 insertions(+), 135 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java index b9907238d..cc567e682 100644 --- a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java +++ b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java @@ -17,26 +17,12 @@ */ package ortus.boxlang.runtime.interceptors; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ServiceLoader; - -import org.apache.commons.io.FilenameUtils; -import org.slf4j.LoggerFactory; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.spi.Configurator; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.events.BaseInterceptor; import ortus.boxlang.runtime.events.InterceptionPoint; -import ortus.boxlang.runtime.logging.LogLevel; -import ortus.boxlang.runtime.logging.LoggingConfigurator; import ortus.boxlang.runtime.logging.LoggingService; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * A BoxLang interceptor that provides logging capabilities @@ -78,36 +64,23 @@ public Logging( BoxRuntime instance ) { @InterceptionPoint public void logMessage( IStruct data ) { // The incoming data - String logCategory = ( String ) data.getOrDefault( Key.applicationName, LoggingService.DEFAULT_LOG_CATEGORY ); - String logText = ( String ) data.getOrDefault( Key.text, "" ); - String logType = ( String ) data.getOrDefault( Key.type, LoggingService.DEFAULT_LOG_LEVEL ); - String logFile = ( String ) data.getOrDefault( Key.file, "" ); - String compatLog = ( String ) data.getOrDefault( Key.log, "" ); + String applicationName = ( String ) data.getOrDefault( Key.applicationName, LoggingService.DEFAULT_LOG_CATEGORY ); + String logText = ( String ) data.getOrDefault( Key.text, "" ); + String logType = ( String ) data.getOrDefault( Key.type, LoggingService.DEFAULT_LOG_LEVEL ); + String logFile = ( String ) data.getOrDefault( Key.file, "" ); + String compatLog = ( String ) data.getOrDefault( Key.log, "" ); // If the logText is empty, then don't log anything if ( logText.isEmpty() ) { return; } - // Default to info if no log level is passed - if ( logType.isEmpty() ) { - logType = LoggingService.DEFAULT_LOG_LEVEL; - } - // Get and Validate log level - Key logLevel = LogLevel.valueOf( logType, false ); - - // The application name is used as the logging category. - // If it is empty, then use the default category - if ( logCategory.isEmpty() ) { - logCategory = LoggingService.DEFAULT_LOG_CATEGORY; - } if ( logFile == null ) { logFile = ""; } if ( compatLog == null ) { compatLog = ""; } - // COMPAT MODE: If we have an incoming `log` key, then we need to map it to a file. // This is a dumb feature and should be moved to the CFML compatibility module // As per the CFML docs, if the file is passed, then ignore this @@ -115,95 +88,13 @@ public void logMessage( IStruct data ) { logFile = compatLog.toLowerCase(); } - // If no file or log is passed, then use the default log file: boxruntime.log - if ( logFile.isEmpty() ) { - logFile = LoggingService.DEFAULT_LOG_CATEGORY + ".log"; - } - - // Verify the log file ends in `.log` and if not, append it - if ( !logFile.toLowerCase().endsWith( ".log" ) ) { - logFile += ".log"; - } - - // If the file is an absolute path, use it, otherwise use the logs directory as the base - String filePath = Path.of( logFile ).isAbsolute() - ? Path.of( logFile ).normalize().toString() - : Paths.get( loggingService.getLogsDirectory(), "/", logFile ).normalize().toString(); - - try { - // Build the logger context or get it if it exists - LoggerContext logContext = getOrBuildLoggerContext(); - // Now that we have a context - // A logger is based on the {fileName} as the category. This allows multiple loggers - // for the same file, but different categories - final Logger logger = logContext.getLogger( FilenameUtils.getBaseName( filePath ).toLowerCase() ); - logger.setLevel( Level.TRACE ); - logger.setAdditive( true ); - - // Create or compute the file appender requested - // This provides locking also and caching so we don't have to keep creating them - // Shutdown will stop the appenders - logger.addAppender( - this.loggingService.getOrBuildAppender( filePath, logContext ) - ); - - // Log according to the level - switch ( logLevel.getNameNoCase() ) { - // No fatal in SL4J - case "FATAL" -> logger.error( logText ); - case "ERROR" -> logger.error( logText ); - case "WARN" -> logger.warn( logText ); - case "INFO" -> logger.info( logText ); - case "DEBUG" -> logger.debug( logText ); - case "TRACE" -> logger.trace( logText ); - default -> logger.info( logText ); - } - - } catch ( Exception e ) { - throw new BoxRuntimeException( "An error occurred while attempting to log the message", e ); - } - - } - - /** - * Gets the logger context or builds one if it doesn't exist (Usually in a servlet context) - * TODO: Move to the service - * - * @return The logger context - */ - private LoggerContext getOrBuildLoggerContext() { - LoggerContext logContext = null; - org.slf4j.ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - - // If our core SLF4J logger factory is returning a logback instance use that - if ( loggerFactory instanceof LoggerContext ) { - return ( LoggerContext ) loggerFactory; - } - - // Log the issue and try to get the context from the configurator - loggerFactory - .getLogger( getClass().getName() ) - .warn( "The LoggerFactory context is not an instance of Logback LoggerContext. Received class: {}", loggerFactory.getClass().getName() ); - - // otherwise grab the context from the configurator - LoggingConfigurator configurator = ServiceLoader - .load( Configurator.class, BoxRuntime.class.getClassLoader() ) - .stream() - .map( ServiceLoader.Provider::get ) - .map( LoggingConfigurator.class::cast ) - .findFirst() - .orElse( null ); - - logContext = configurator.getLoggerContext(); - - // In the servlet context we are seeing the configurator configure method is not being run automagically - if ( logContext == null ) { - logContext = new LoggerContext(); - logContext.start(); - configurator.configure( logContext ); - } + LoggingService.getInstance().logMessage( + logText, + logType, + applicationName, + logFile + ); - return logContext; } /** diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java index f0b2e50cf..a0bd84c00 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java @@ -29,27 +29,21 @@ import ortus.boxlang.runtime.BoxRuntime; /** - * Configures the bundled SLF4J provider via logback. - * - * THIS CLASS IS CALLED AUTOMATICALLY BY LOGBACK'S SERVICE LOADER MECHANISM. - * - * This class serves as a single endpoint for configuring the slf4j logging - * provider. Currently that is logback, but in the future it may be another - * provider. - * + * This class configures LogBack for the BoxLang runtime. + *

+ * THIS CLASS IS CALLED AUTOMATICALLY BY LOGBACK'S SERVICE LOADER MECHANISM. + *

* See https://logback.qos.ch/manual/configuration.html for more information on * logback configuration. */ public class LoggingConfigurator extends LoggerContextAwareBase implements Configurator { /** - * Logback-specific encoder pattern. Thankfully, this is fairly legible compared - * to the JUL pattern. - * https://logback.qos.ch/manual/layouts.html#conversionWord + * The log format for the BoxLang runtime * * @see https://logback.qos.ch/manual/layouts.html#conversionWord */ - public static final String LOG_FORMAT = "[%date{STRICT}] [%highlight(%-5level)] [%thread] [%logger{0}] %kvp %message%n"; + public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %kvp %message%n"; /** * The name of the context for the runtime @@ -78,7 +72,7 @@ public LoggingConfigurator() { public ExecutionStatus configure( LoggerContext loggerContext ) { loggerContext.setName( CONTEXT_NAME ); - // Setup the encoder + // Setup the runtime encoder with the BoxLang format var encoder = new PatternLayoutEncoder(); encoder.setContext( loggerContext ); encoder.setPattern( LOG_FORMAT ); @@ -88,7 +82,7 @@ public ExecutionStatus configure( LoggerContext loggerContext ) { var debugMode = BoxRuntime.getInstance().inDebugMode(); Level logLevel = Boolean.TRUE.equals( debugMode ) ? Level.DEBUG : Level.WARN; - // Configure a Console Appender + // Configure The Console Appender // See: https://logback.qos.ch/manual/appenders.html ConsoleAppender appender = new ConsoleAppender<>(); appender.setContext( loggerContext ); @@ -105,6 +99,7 @@ public ExecutionStatus configure( LoggerContext loggerContext ) { // Store the necessary configuration for the runtime LoggingService.getInstance() + .setLoggerContext( loggerContext ) .setLoggingConfigurator( this ) .setEncoder( encoder ) .setRootLogger( rootLogger ); diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index b2a672ce1..3be67368f 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -17,18 +17,26 @@ */ package ortus.boxlang.runtime.logging; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; +import org.apache.commons.io.FilenameUtils; +import org.slf4j.LoggerFactory; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.Configurator; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.FileAppender; import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.scopes.Key; /** * This service allows BoxLang to leverage logging facilities and interact with the logging system. @@ -55,6 +63,9 @@ public class LoggingService { * -------------------------------------------------------------------------- */ + /** + * Singleton + */ private static LoggingService instance; /** @@ -77,6 +88,11 @@ public class LoggingService { */ private Logger rootLogger; + /** + * The logger context for the runtime + */ + private LoggerContext loggerContext; + /** * The BoxLang pattern encoder we use for logging */ @@ -141,7 +157,7 @@ public LoggingConfigurator getLoggingConfigurator() { /** * -------------------------------------------------------------------------- - * Methods + * Getters & Setters * -------------------------------------------------------------------------- */ @@ -199,6 +215,33 @@ public LoggingService setLoggingConfigurator( LoggingConfigurator configurator ) return instance; } + /** + * Get the logger context + * + * @return The logger context + */ + public LoggerContext getLoggerContext() { + return this.loggerContext; + } + + /** + * Set the logger context + * + * @param loggerContext The logger context to set + * + * @return The logging service + */ + public LoggingService setLoggerContext( LoggerContext loggerContext ) { + this.loggerContext = loggerContext; + return instance; + } + + /** + * -------------------------------------------------------------------------- + * Runtime Methods + * -------------------------------------------------------------------------- + */ + /** * Enable debug mode for the runtime's root logger or not. * @@ -213,6 +256,77 @@ public LoggingService reconfigureDebugMode( Boolean debugMode ) { return instance; } + /** + * Log a message into a specific log file and a specific type + * + * @param message The message to log + * @param type The type of log message (fatal, error, info, warn, debug, trace) + * @param applicationName The name of the application requesting the log message + * @param logFile The destination file to log to in the logs directory. If empty, the default log file is used + * + * @return The logging service + */ + public LoggingService logMessage( + String message, + String type, + String applicationName, + String logFile ) { + + // Default to info if no log level is passed + if ( type.isEmpty() ) { + type = DEFAULT_LOG_LEVEL; + } + // Get and Validate log level + Key logLevel = LogLevel.valueOf( type, false ); + + // The application name is used as the logging category. + // If it is empty, then use the default category + if ( applicationName.isEmpty() ) { + applicationName = DEFAULT_LOG_CATEGORY; + } + + // If no file or log is passed, then use the default log file: boxruntime.log + if ( logFile.isEmpty() ) { + logFile = LoggingService.DEFAULT_LOG_CATEGORY + ".log"; + } + + // Verify the log file ends in `.log` and if not, append it + if ( !logFile.toLowerCase().endsWith( ".log" ) ) { + logFile += ".log"; + } + + // If the file is an absolute path, use it, otherwise use the logs directory as the base + String filePath = Path.of( logFile ).isAbsolute() + ? Path.of( logFile ).normalize().toString() + : Paths.get( getLogsDirectory(), "/", logFile ).normalize().toString(); + + // Build the logger context or get it if it exists + // Now that we have a context + // A logger is based on the {fileName} as the category. This allows multiple loggers + // for the same file, but different categories + final Logger logger = getLoggerContext().getLogger( FilenameUtils.getBaseName( filePath ).toLowerCase() ); + logger.setLevel( Level.TRACE ); + + // Create or compute the file appender requested + // This provides locking also and caching so we don't have to keep creating them + // Shutdown will stop the appenders + logger.addAppender( getOrBuildAppender( filePath, getLoggerContext() ) ); + + // Log according to the level + switch ( logLevel.getNameNoCase() ) { + // No fatal in SL4J + case "FATAL" -> logger.error( message ); + case "ERROR" -> logger.error( message ); + case "WARN" -> logger.warn( message ); + case "INFO" -> logger.info( message ); + case "DEBUG" -> logger.debug( message ); + case "TRACE" -> logger.trace( message ); + default -> logger.info( message ); + } + + return instance; + } + /** * Get the runtime's log directory as per the configuration */ @@ -288,7 +402,54 @@ public LoggingService shutdownAppenders() { public LoggingService shutdown() { // Shutdown all the appenders shutdownAppenders(); + this.loggerContext.stop(); return instance; } + /** + * -------------------------------------------------------------------------- + * Private Methods + * -------------------------------------------------------------------------- + */ + + /** + * Gets the logger context or builds one if it doesn't exist (Usually in a servlet context) + * + * @return The logger context + */ + private LoggerContext getOrBuildLoggerContext() { + LoggerContext logContext = null; + org.slf4j.ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + + // If our core SLF4J logger factory is returning a logback instance use that + if ( loggerFactory instanceof LoggerContext ) { + return ( LoggerContext ) loggerFactory; + } + + // Log the issue and try to get the context from the configurator + loggerFactory + .getLogger( getClass().getName() ) + .warn( "The LoggerFactory context is not an instance of Logback LoggerContext. Received class: {}", loggerFactory.getClass().getName() ); + + // otherwise grab the context from the configurator + LoggingConfigurator configurator = ServiceLoader + .load( Configurator.class, BoxRuntime.class.getClassLoader() ) + .stream() + .map( ServiceLoader.Provider::get ) + .map( LoggingConfigurator.class::cast ) + .findFirst() + .orElse( null ); + + logContext = configurator.getLoggerContext(); + + // In the servlet context we are seeing the configurator configure method is not being run automagically + if ( logContext == null ) { + logContext = new LoggerContext(); + logContext.start(); + configurator.configure( logContext ); + } + + return logContext; + } + } From c6b220ed701dd3498a35155151fc00ed282df3e3 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Mon, 25 Nov 2024 11:07:07 -0500 Subject: [PATCH 11/38] resolves an issue where the body did not write to the buffer on BX components --- .../boxlang/runtime/modules/ModuleRecord.java | 13 ++++++++++--- .../java/ortus/boxlang/runtime/scopes/Key.java | 1 + .../boxlang/runtime/types/DynamicFunction.java | 16 ++++++++++++++++ .../runtime/modules/ModuleRecordTest.java | 18 ++++++++++-------- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java index d06f1d6fd..762c74d6d 100644 --- a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java +++ b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java @@ -57,6 +57,7 @@ import ortus.boxlang.runtime.loader.DynamicClassLoader; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.runnables.RunnableLoader; +import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.scopes.ArgumentsScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.scopes.ThisScope; @@ -792,13 +793,19 @@ private ModuleRecord registerComponent( File targetFile, IBoxContext context ) { Key.processBody, ( context1, fnc ) -> { ArgumentsScope args = context1.getArgumentsScope(); - Object buffer = args.get( "buffer" ); + + Object buffer = args.get( Key.buffer ); return oComponentProxy.processBody( - ( IBoxContext ) args.get( "context" ), - ( ComponentBody ) args.get( "body" ), + ( IBoxContext ) args.get( Key.context ), + ( ComponentBody ) args.get( Key.body ), buffer instanceof StringBuffer ? ( StringBuffer ) buffer : new StringBuffer() ); + }, + new Argument[] { + new Argument( true, "any", Key.context ), + new Argument( true, "any", Key.body ), + new Argument( true, "any", Key.buffer ) } ) ); // Get Name Delegate diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index dac41de22..14564325f 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -128,6 +128,7 @@ public class Key implements Comparable, Serializable { public static final Key bxSessions = Key.of( "bxSessions" ); public static final Key boxMember = Key.of( "BoxMember" ); public static final Key boxRuntime = Key.of( "boxRuntime" ); + public static final Key buffer = Key.of( "buffer" ); public static final Key buffersize = Key.of( "buffersize" ); public static final Key bxDefaultDatasource = Key.of( "bxDefaultDatasource" ); public static final Key bxRandomSeed = Key.of( "bxRandomSeed" ); diff --git a/src/main/java/ortus/boxlang/runtime/types/DynamicFunction.java b/src/main/java/ortus/boxlang/runtime/types/DynamicFunction.java index e628fc5aa..add87b62c 100644 --- a/src/main/java/ortus/boxlang/runtime/types/DynamicFunction.java +++ b/src/main/java/ortus/boxlang/runtime/types/DynamicFunction.java @@ -120,6 +120,22 @@ public DynamicFunction( this.documentation.put( "hint", hint ); } + /** + * Simple Constructor of just the name and the target and arguments + * + * @param name The name of the function that will be used to register it + * @param target The target lambda that will be executed when the function is called + * @param arguments The arguments of the function + */ + public DynamicFunction( + Key name, + BiFunction target, + Argument[] arguments ) { + this.name = name; + this.target = target; + this.arguments = arguments; + } + /** * Simple Constructor of just the name and the target * diff --git a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java index 5fb9a7f71..cb1708de2 100644 --- a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java +++ b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java @@ -251,13 +251,6 @@ void testItCanActivateAModule() throws ClassNotFoundException { """, context ); // @formatter:on - // Test the component - // @formatter:off - runtime.executeSource(""" - Hello - """, context, BoxSourceType.BOXTEMPLATE ); - // @formatter:on - IScope variables = context.getScopeNearby( VariablesScope.name ); assertThat( variables.getAsString( Key.result ) ) .isEqualTo( "Hello World, my name is boxlang and I am 0 years old" ); @@ -265,7 +258,16 @@ void testItCanActivateAModule() throws ClassNotFoundException { assertThat( variables.getAsString( Key.of( "result3" ) ) ).isEqualTo( "Hello World, my name is boxlang and I am 0 years old" ); - // assertThat( variables.getAsString( Key.of( "result4" ) ) ).isEqualTo( "Hola Mundo!" ); + + // Test the component body execution + // @formatter:off + runtime.executeSource(""" + Hello + + """, context, BoxSourceType.BOXTEMPLATE ); + // @formatter:on + // The buffer is reversed on this component so it should be backwards + assertThat( variables.getAsString( Key.of( "result4" ) ).trim() ).isEqualTo( "olleH" ); // Test Module Class Locators // @formatter:off From 6205cdd9866c463c5b5aaa7e185e307e7b748079 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:59:46 +0000 Subject: [PATCH 12/38] Bump commons-io:commons-io from 2.17.0 to 2.18.0 Bumps commons-io:commons-io from 2.17.0 to 2.18.0. --- updated-dependencies: - dependency-name: commons-io:commons-io dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 903688071..9571ddf9f 100644 --- a/build.gradle +++ b/build.gradle @@ -107,7 +107,7 @@ dependencies { // Implementation Dependencies // https://mvnrepository.com/artifact/commons-io/commons-io - implementation "commons-io:commons-io:2.17.0" + implementation "commons-io:commons-io:2.18.0" // https://mvnrepository.com/artifact/com.github.javaparser/javaparser-symbol-solver-core implementation 'com.github.javaparser:javaparser-symbol-solver-core:3.26.2' // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 From c95db6f350c4a36b38e9fef8ab92c18b87549754 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 21:31:26 +0100 Subject: [PATCH 13/38] small updates for servlet to work until we refactor it to use a standardize logging config --- .../boxlang/runtime/logging/LoggingConfigurator.java | 2 +- .../ortus/boxlang/runtime/logging/LoggingService.java | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java index a0bd84c00..a80dc64cb 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java @@ -58,7 +58,7 @@ public LoggingConfigurator() { } /** - * Configure the logging provider. + * Configures the basic logging for the BoxLang runtime. *

* This is called by the logback service loader mechanism. *

diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 3be67368f..251bc9c70 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -131,6 +131,11 @@ public static LoggingService getInstance( BoxRuntime runtime ) { return instance; } + public LoggingService loadConfiguration() { + LoggerContext context = ( LoggerContext ) LoggerFactory.getILoggerFactory(); + return instance; + } + /** * Get the singleton instance of the LoggingService * @@ -221,6 +226,9 @@ public LoggingService setLoggingConfigurator( LoggingConfigurator configurator ) * @return The logger context */ public LoggerContext getLoggerContext() { + if ( this.loggerContext == null ) { + this.loggerContext = getOrBuildLoggerContext(); + } return this.loggerContext; } @@ -304,7 +312,7 @@ public LoggingService logMessage( // Now that we have a context // A logger is based on the {fileName} as the category. This allows multiple loggers // for the same file, but different categories - final Logger logger = getLoggerContext().getLogger( FilenameUtils.getBaseName( filePath ).toLowerCase() ); + final Logger logger = getOrBuildLoggerContext().getLogger( FilenameUtils.getBaseName( filePath ).toLowerCase() ); logger.setLevel( Level.TRACE ); // Create or compute the file appender requested From 6e94fcd1cceb39f205ac438b02cd0e24b9eb5f3d Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 22:31:34 +0100 Subject: [PATCH 14/38] trying more things out for servlets --- .../runtime/logging/LoggingService.java | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 251bc9c70..5c0add32d 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.io.FilenameUtils; @@ -32,7 +31,6 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; -import ch.qos.logback.classic.spi.Configurator; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.FileAppender; import ortus.boxlang.runtime.BoxRuntime; @@ -439,25 +437,33 @@ private LoggerContext getOrBuildLoggerContext() { .getLogger( getClass().getName() ) .warn( "The LoggerFactory context is not an instance of Logback LoggerContext. Received class: {}", loggerFactory.getClass().getName() ); - // otherwise grab the context from the configurator - LoggingConfigurator configurator = ServiceLoader - .load( Configurator.class, BoxRuntime.class.getClassLoader() ) - .stream() - .map( ServiceLoader.Provider::get ) - .map( LoggingConfigurator.class::cast ) - .findFirst() - .orElse( null ); - - logContext = configurator.getLoggerContext(); - - // In the servlet context we are seeing the configurator configure method is not being run automagically - if ( logContext == null ) { - logContext = new LoggerContext(); - logContext.start(); - configurator.configure( logContext ); + // Do we have a configurator? + if ( this.loggingConfigurator == null ) { + synchronized ( this ) { + if ( this.loggingConfigurator == null ) { + // In the servlet context we are seeing the configurator configure method is not being run automagically + loggerFactory + .getLogger( getClass().getName() ) + .warn( "Log context was null, attempting to configure the logger context manually" ); + + logContext = new LoggerContext(); + logContext.start(); + var newLoggingConfig = new LoggingConfigurator(); + newLoggingConfig.configure( logContext ); + this.loggingConfigurator = newLoggingConfig; + } + } } - return logContext; + return getLoggingConfigurator().getLoggerContext(); + // LoggingConfigurator configurator = ServiceLoader + // .load( Configurator.class, BoxRuntime.class.getClassLoader() ) + // .stream() + // .map( ServiceLoader.Provider::get ) + // .map( LoggingConfigurator.class::cast ) + // .findFirst() + // .orElse( null ); + // logContext = configurator.getLoggerContext(); } } From d2e504a30106a0d212ab856f7dd5e8f9c3153038 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 22:57:08 +0100 Subject: [PATCH 15/38] more fine tuning on logging output --- .../java/ortus/boxlang/runtime/interceptors/Logging.java | 2 +- .../boxlang/runtime/logging/LoggingConfigurator.java | 2 +- .../ortus/boxlang/runtime/logging/LoggingService.java | 8 +++++--- .../boxlang/runtime/bifs/global/system/WriteLogTest.java | 9 ++++++++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java index cc567e682..34c997b48 100644 --- a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java +++ b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java @@ -64,7 +64,7 @@ public Logging( BoxRuntime instance ) { @InterceptionPoint public void logMessage( IStruct data ) { // The incoming data - String applicationName = ( String ) data.getOrDefault( Key.applicationName, LoggingService.DEFAULT_LOG_CATEGORY ); + String applicationName = data.getAsString( Key.applicationName ); String logText = ( String ) data.getOrDefault( Key.text, "" ); String logType = ( String ) data.getOrDefault( Key.type, LoggingService.DEFAULT_LOG_LEVEL ); String logFile = ( String ) data.getOrDefault( Key.file, "" ); diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java index a80dc64cb..3f450ac63 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java @@ -43,7 +43,7 @@ public class LoggingConfigurator extends LoggerContextAwareBase implements Confi * * @see https://logback.qos.ch/manual/layouts.html#conversionWord */ - public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %kvp %message%n"; + public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message%n"; /** * The name of the context for the runtime diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 5c0add32d..be4fbcf1f 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -279,7 +279,7 @@ public LoggingService logMessage( String logFile ) { // Default to info if no log level is passed - if ( type.isEmpty() ) { + if ( type == null || type.isEmpty() ) { type = DEFAULT_LOG_LEVEL; } // Get and Validate log level @@ -287,9 +287,11 @@ public LoggingService logMessage( // The application name is used as the logging category. // If it is empty, then use the default category - if ( applicationName.isEmpty() ) { - applicationName = DEFAULT_LOG_CATEGORY; + if ( applicationName == null || applicationName.isEmpty() ) { + applicationName = "no-application"; } + // Include the application name in the message + message = String.format( "[%s] %s", applicationName, message ); // If no file or log is passed, then use the default log file: boxruntime.log if ( logFile.isEmpty() ) { diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java index f0be8ae34..b27354028 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java @@ -87,11 +87,18 @@ public void setupEach() { @DisplayName( "It can write to the default log file" ) @Test public void testPrint() { + // @formatter:off instance.executeSource( """ - writeLog( "Hello Logger!" ) + writeLog( "Hello Logger!" ) + writeLog( "Hola!" ) + writeLog( text="Hola debug", type="Debug" ) + writeLog( text="Hola debug", type="error" ) + writeLog( text="Hola debug", type="warning" ) + writeLog( text="Hola debug", type="info" ) """, context ); + // @formatter:on // Assert we got here assertThat( FileSystemUtil.exists( defaultLogFilePath ) ).isTrue(); From 132c25296aa3c013281e730ecaecc6d167c33971 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 25 Nov 2024 23:02:55 +0100 Subject: [PATCH 16/38] add exception tracking to logging --- .../java/ortus/boxlang/runtime/logging/LoggingConfigurator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java index 3f450ac63..9a05219e8 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java @@ -43,7 +43,7 @@ public class LoggingConfigurator extends LoggerContextAwareBase implements Confi * * @see https://logback.qos.ch/manual/layouts.html#conversionWord */ - public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message%n"; + public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message %ex%n"; /** * The name of the context for the runtime From 5f1fd3e02a1d25a8310f5119ca4381256dc53715 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 26 Nov 2024 14:09:30 +0100 Subject: [PATCH 17/38] fixing application name which comes as a key not a string. --- src/main/java/ortus/boxlang/runtime/interceptors/Logging.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java index 34c997b48..cda2dd62a 100644 --- a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java +++ b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java @@ -64,11 +64,11 @@ public Logging( BoxRuntime instance ) { @InterceptionPoint public void logMessage( IStruct data ) { // The incoming data - String applicationName = data.getAsString( Key.applicationName ); String logText = ( String ) data.getOrDefault( Key.text, "" ); String logType = ( String ) data.getOrDefault( Key.type, LoggingService.DEFAULT_LOG_LEVEL ); String logFile = ( String ) data.getOrDefault( Key.file, "" ); String compatLog = ( String ) data.getOrDefault( Key.log, "" ); + Object applicationName = data.get( Key.applicationName ); // If the logText is empty, then don't log anything if ( logText.isEmpty() ) { @@ -91,7 +91,7 @@ public void logMessage( IStruct data ) { LoggingService.getInstance().logMessage( logText, logType, - applicationName, + ( applicationName instanceof Key ) ? ( ( Key ) applicationName ).getName() : "", logFile ); From a994415d5cfe0cccf2bb097abdb54d477424882e Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 26 Nov 2024 15:23:32 +0100 Subject: [PATCH 18/38] BL-782 removal of service loader approach for config due to different loading if in servlet or non-servlet mode. --- .../ortus/boxlang/runtime/BoxRuntime.java | 2 +- .../runtime/logging/LoggingConfigurator.java | 126 --------------- .../runtime/logging/LoggingService.java | 148 +++++++----------- .../ch.qos.logback.classic.spi.Configurator | 1 - 4 files changed, 58 insertions(+), 219 deletions(-) delete mode 100644 src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java delete mode 100644 src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index 33b558fbe..88d15478c 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -455,7 +455,7 @@ private void startup( Boolean debugMode ) { timerUtil.start( "runtime-startup" ); // Startup the Logging Service: Unique as it's not an IService - this.loggingService = LoggingService.getInstance( this ); + this.loggingService = LoggingService.getInstance( this ).configureBasic( debugMode ); // Startup basic logging // Here is where LogBack looks via ServiceLoader for a `Configurator` class diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java deleted file mode 100644 index 9a05219e8..000000000 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingConfigurator.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ortus.boxlang.runtime.logging; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.encoder.PatternLayoutEncoder; -import ch.qos.logback.classic.spi.Configurator; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.classic.spi.LoggerContextAwareBase; -import ch.qos.logback.core.ConsoleAppender; -import ch.qos.logback.core.Context; -import ortus.boxlang.runtime.BoxRuntime; - -/** - * This class configures LogBack for the BoxLang runtime. - *

- * THIS CLASS IS CALLED AUTOMATICALLY BY LOGBACK'S SERVICE LOADER MECHANISM. - *

- * See https://logback.qos.ch/manual/configuration.html for more information on - * logback configuration. - */ -public class LoggingConfigurator extends LoggerContextAwareBase implements Configurator { - - /** - * The log format for the BoxLang runtime - * - * @see https://logback.qos.ch/manual/layouts.html#conversionWord - */ - public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message %ex%n"; - - /** - * The name of the context for the runtime - */ - public static final String CONTEXT_NAME = "BoxLang"; - - /** - * Default constructor needed by logback - */ - public LoggingConfigurator() { - // Needed by logback - } - - /** - * Configures the basic logging for the BoxLang runtime. - *

- * This is called by the logback service loader mechanism. - *

- * This initializes the runtime's basic logging conifguration which can be - * expanded on. - * - * @param loggerContext The logger context to configure - * - * @return The status of the configuration @see ExecutionStatus - */ - public ExecutionStatus configure( LoggerContext loggerContext ) { - loggerContext.setName( CONTEXT_NAME ); - - // Setup the runtime encoder with the BoxLang format - var encoder = new PatternLayoutEncoder(); - encoder.setContext( loggerContext ); - encoder.setPattern( LOG_FORMAT ); - encoder.start(); - - // Base log level depending on debug mode - var debugMode = BoxRuntime.getInstance().inDebugMode(); - Level logLevel = Boolean.TRUE.equals( debugMode ) ? Level.DEBUG : Level.WARN; - - // Configure The Console Appender - // See: https://logback.qos.ch/manual/appenders.html - ConsoleAppender appender = new ConsoleAppender<>(); - appender.setContext( loggerContext ); - appender.setEncoder( encoder ); - appender.start(); - - // Configure the Root Logger - var rootLogger = loggerContext.getLogger( Logger.ROOT_LOGGER_NAME ); - rootLogger.setLevel( logLevel ); - rootLogger.addAppender( appender ); - - // Set the logger context back - setLoggerContext( loggerContext ); - - // Store the necessary configuration for the runtime - LoggingService.getInstance() - .setLoggerContext( loggerContext ) - .setLoggingConfigurator( this ) - .setEncoder( encoder ) - .setRootLogger( rootLogger ); - - // We should be the last configurator to run, so stop searching for further - // configurators. - return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY; - } - - /** - * Override to set the context on the encoder - */ - @Override - public void setContext( Context context ) { - if ( this.context == null ) { - this.context = context; - } else if ( this.context != context ) { - getLoggerContext() - .getLogger( Logger.ROOT_LOGGER_NAME ) - .error( "LoggingConfigurator context has been already set and an attempt to overwrite was made." ); - } - } - -} diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index be4fbcf1f..47c82af46 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.io.FilenameUtils; +import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; @@ -32,6 +33,7 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.FileAppender; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.scopes.Key; @@ -54,6 +56,14 @@ public class LoggingService { public static final String DEFAULT_LOG_LEVEL = "info"; public static final String DEFAULT_LOG_TYPE = "Application"; public static final String DEFAULT_LOG_CATEGORY = "boxruntime"; + public static final String CONTEXT_NAME = "BoxLang"; + + /** + * The log format for the BoxLang runtime + * + * @see https://logback.qos.ch/manual/layouts.html#conversionWord + */ + public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message %ex%n"; /** * -------------------------------------------------------------------------- @@ -71,16 +81,6 @@ public class LoggingService { */ private BoxRuntime runtime; - /** - * The logging configuration for the runtime - * This is set by the first call to `getLogger()` which happens in the - * - *

-	 * startup()
-	 * 
- */ - private LoggingConfigurator loggingConfigurator; - /** * The root logger for the runtime */ @@ -129,8 +129,45 @@ public static LoggingService getInstance( BoxRuntime runtime ) { return instance; } - public LoggingService loadConfiguration() { - LoggerContext context = ( LoggerContext ) LoggerFactory.getILoggerFactory(); + /** + * This configures LogBack with a basic configuration, so we can use logging before we actually read + * the configuration file. + *

+ * Once the configuration file is read, we can reconfigure the logging system. + * + * @param debugMode The flag the runtime was started with + */ + public LoggingService configureBasic( Boolean debugMode ) { + ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + + // Are we in Servlet mode or not? If we are not, then we have to build the logger context + if ( loggerFactory instanceof LoggerContext ) { + this.loggerContext = ( LoggerContext ) loggerFactory; + } else { + this.loggerContext = new LoggerContext(); + } + + // Name it + this.loggerContext.setName( CONTEXT_NAME ); + + // Setup the runtime encoder with the BoxLang format + this.encoder = new PatternLayoutEncoder(); + this.encoder.setContext( loggerContext ); + this.encoder.setPattern( LOG_FORMAT ); + this.encoder.start(); + + // Configure a basic Console Appender + // See: https://logback.qos.ch/manual/appenders.html + ConsoleAppender appender = new ConsoleAppender<>(); + appender.setContext( this.loggerContext ); + appender.setEncoder( this.encoder ); + appender.start(); + + // Configure the Root Logger + this.rootLogger = loggerContext.getLogger( Logger.ROOT_LOGGER_NAME ); + this.rootLogger.setLevel( Boolean.TRUE.equals( debugMode ) ? Level.DEBUG : Level.WARN ); + this.rootLogger.addAppender( appender ); + return instance; } @@ -149,15 +186,6 @@ public static LoggingService getInstance() { return instance; } - /** - * Get the configured Logging Configurator for the runtime - * - * @return The Configurator - */ - public LoggingConfigurator getLoggingConfigurator() { - return this.loggingConfigurator; - } - /** * -------------------------------------------------------------------------- * Getters & Setters @@ -206,27 +234,12 @@ public LoggingService setEncoder( PatternLayoutEncoder encoder ) { return instance; } - /** - * Set the Logging Configurator - * - * @param configurator The configurator to set - * - * @return The Runtime - */ - public LoggingService setLoggingConfigurator( LoggingConfigurator configurator ) { - this.loggingConfigurator = configurator; - return instance; - } - /** * Get the logger context * * @return The logger context */ public LoggerContext getLoggerContext() { - if ( this.loggerContext == null ) { - this.loggerContext = getOrBuildLoggerContext(); - } return this.loggerContext; } @@ -304,21 +317,22 @@ public LoggingService logMessage( } // If the file is an absolute path, use it, otherwise use the logs directory as the base - String filePath = Path.of( logFile ).isAbsolute() + String filePath = Path.of( logFile ).isAbsolute() ? Path.of( logFile ).normalize().toString() : Paths.get( getLogsDirectory(), "/", logFile ).normalize().toString(); - // Build the logger context or get it if it exists - // Now that we have a context - // A logger is based on the {fileName} as the category. This allows multiple loggers - // for the same file, but different categories - final Logger logger = getOrBuildLoggerContext().getLogger( FilenameUtils.getBaseName( filePath ).toLowerCase() ); + // A BoxLang logger is based on the {fileName} as the category. + LoggerContext targetContext = getLoggerContext(); + Logger logger = targetContext.getLogger( FilenameUtils.getBaseName( filePath ).toLowerCase() ); logger.setLevel( Level.TRACE ); + FileAppender appender = getOrBuildAppender( filePath, targetContext ); // Create or compute the file appender requested // This provides locking also and caching so we don't have to keep creating them // Shutdown will stop the appenders - logger.addAppender( getOrBuildAppender( filePath, getLoggerContext() ) ); + if ( !logger.isAttached( appender ) ) { + logger.addAppender( appender ); + } // Log according to the level switch ( logLevel.getNameNoCase() ) { @@ -420,52 +434,4 @@ public LoggingService shutdown() { * -------------------------------------------------------------------------- */ - /** - * Gets the logger context or builds one if it doesn't exist (Usually in a servlet context) - * - * @return The logger context - */ - private LoggerContext getOrBuildLoggerContext() { - LoggerContext logContext = null; - org.slf4j.ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - - // If our core SLF4J logger factory is returning a logback instance use that - if ( loggerFactory instanceof LoggerContext ) { - return ( LoggerContext ) loggerFactory; - } - - // Log the issue and try to get the context from the configurator - loggerFactory - .getLogger( getClass().getName() ) - .warn( "The LoggerFactory context is not an instance of Logback LoggerContext. Received class: {}", loggerFactory.getClass().getName() ); - - // Do we have a configurator? - if ( this.loggingConfigurator == null ) { - synchronized ( this ) { - if ( this.loggingConfigurator == null ) { - // In the servlet context we are seeing the configurator configure method is not being run automagically - loggerFactory - .getLogger( getClass().getName() ) - .warn( "Log context was null, attempting to configure the logger context manually" ); - - logContext = new LoggerContext(); - logContext.start(); - var newLoggingConfig = new LoggingConfigurator(); - newLoggingConfig.configure( logContext ); - this.loggingConfigurator = newLoggingConfig; - } - } - } - - return getLoggingConfigurator().getLoggerContext(); - // LoggingConfigurator configurator = ServiceLoader - // .load( Configurator.class, BoxRuntime.class.getClassLoader() ) - // .stream() - // .map( ServiceLoader.Provider::get ) - // .map( LoggingConfigurator.class::cast ) - // .findFirst() - // .orElse( null ); - // logContext = configurator.getLoggerContext(); - } - } diff --git a/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator deleted file mode 100644 index 76fd850db..000000000 --- a/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator +++ /dev/null @@ -1 +0,0 @@ -ortus.boxlang.runtime.logging.LoggingConfigurator \ No newline at end of file From 911a2639e76dc5d6c6828d55b18dd5d724356e76 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 26 Nov 2024 15:36:37 +0100 Subject: [PATCH 19/38] BL-782 Making sure we store appenders, in abstract --- .../runtime/logging/LoggingService.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 47c82af46..45c64744b 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -33,6 +33,7 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.FileAppender; import ortus.boxlang.runtime.BoxRuntime; @@ -53,17 +54,17 @@ public class LoggingService { * -------------------------------------------------------------------------- */ - public static final String DEFAULT_LOG_LEVEL = "info"; - public static final String DEFAULT_LOG_TYPE = "Application"; - public static final String DEFAULT_LOG_CATEGORY = "boxruntime"; - public static final String CONTEXT_NAME = "BoxLang"; + public static final String DEFAULT_LOG_LEVEL = "info"; + public static final String DEFAULT_LOG_TYPE = "Application"; + public static final String DEFAULT_LOG_CATEGORY = "boxruntime"; + public static final String CONTEXT_NAME = "BoxLang"; /** * The log format for the BoxLang runtime * * @see https://logback.qos.ch/manual/layouts.html#conversionWord */ - public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message %ex%n"; + public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message %ex%n"; /** * -------------------------------------------------------------------------- @@ -74,32 +75,32 @@ public class LoggingService { /** * Singleton */ - private static LoggingService instance; + private static LoggingService instance; /** * The linked runtime */ - private BoxRuntime runtime; + private BoxRuntime runtime; /** * The root logger for the runtime */ - private Logger rootLogger; + private Logger rootLogger; /** * The logger context for the runtime */ - private LoggerContext loggerContext; + private LoggerContext loggerContext; /** * The BoxLang pattern encoder we use for logging */ - private PatternLayoutEncoder encoder; + private PatternLayoutEncoder encoder; /** - * A map of appenders + * A map of registered appenders */ - private Map> appendersMap = new ConcurrentHashMap<>(); + private Map> appendersMap = new ConcurrentHashMap<>(); /** * -------------------------------------------------------------------------- @@ -366,7 +367,7 @@ public String getLogsDirectory() { * @return The file appender, computed or from cache */ public FileAppender getOrBuildAppender( String filePath, LoggerContext logContext ) { - return this.appendersMap.computeIfAbsent( filePath.toLowerCase(), key -> { + return ( FileAppender ) this.appendersMap.computeIfAbsent( filePath.toLowerCase(), key -> { var appender = new FileAppender(); appender.setFile( filePath ); appender.setEncoder( getEncoder() ); @@ -414,7 +415,7 @@ public List getAppendersList() { * @return The logging service */ public LoggingService shutdownAppenders() { - this.appendersMap.values().forEach( FileAppender::stop ); + this.appendersMap.values().forEach( Appender::stop ); return instance; } From f43b1c21f51c13eb23e983de3692cb044f982b49 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 26 Nov 2024 16:52:54 +0100 Subject: [PATCH 20/38] BL-786 #resolve Change FileAppender to RollingFileAppender --- .../ortus/boxlang/runtime/BoxRuntime.java | 1 + .../boxlang/runtime/interceptors/Logging.java | 17 -------- .../runtime/logging/LoggingService.java | 40 +++++++++++++++++-- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index 88d15478c..1f3c19620 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -961,6 +961,7 @@ public synchronized void shutdown( Boolean force ) { // Shutdown logging instance.logger.debug( "+ BoxLang Runtime has been shutdown" ); + instance.loggingService.shutdown(); // Shutdown the runtime instance = null; diff --git a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java index cda2dd62a..bcb236171 100644 --- a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java +++ b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java @@ -97,21 +97,4 @@ public void logMessage( IStruct data ) { } - /** - * Runtime shutdown interception - */ - @InterceptionPoint - public void onRuntimeShutdown() { - this.loggingService.shutdown(); - } - - /** - * Alternate signature for onRuntimeShutdown - * - * @param data The data to be passed to the interceptor - */ - public void onRuntimeShutdown( IStruct data ) { - onRuntimeShutdown(); - } - } diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 45c64744b..dcb94fa36 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -36,6 +36,9 @@ import ch.qos.logback.core.Appender; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.FileAppender; +import ch.qos.logback.core.rolling.RollingFileAppender; +import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; +import ch.qos.logback.core.util.FileSize; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.scopes.Key; @@ -45,6 +48,11 @@ * It also manages all custom logging events, appenders and loggers. *

* It's not a true BoxLang service, due to the chicken and egg problem of logging being needed before the runtime starts. + *

+ * The configureBasic() method is called by the runtime to setup the basic logging system. + *

+ * Once the runtime is online and has read the configuration file (boxlang.json), it can reconfigure the logging system. + * This could change logging levels, add new appenders, etc. */ public class LoggingService { @@ -368,14 +376,38 @@ public String getLogsDirectory() { */ public FileAppender getOrBuildAppender( String filePath, LoggerContext logContext ) { return ( FileAppender ) this.appendersMap.computeIfAbsent( filePath.toLowerCase(), key -> { - var appender = new FileAppender(); + var appender = new RollingFileAppender(); + String fileName = FilenameUtils.getBaseName( filePath ); + String enclosingFolder = FilenameUtils.getFullPathNoEndSeparator( filePath ); + + // Set basics of appender + appender.setName( "bx." + fileName ); appender.setFile( filePath ); - appender.setEncoder( getEncoder() ); appender.setContext( logContext ); + appender.setEncoder( getEncoder() ); appender.setAppend( true ); appender.setImmediateFlush( true ); - appender.setPrudent( true ); + // This is commented as rolling with compression does not allow prudent handling + // appender.setPrudent( true ); + + // Time-based rolling policy with file size constraint + // TODO: Get from configuration items + SizeAndTimeBasedRollingPolicy policy = new SizeAndTimeBasedRollingPolicy<>(); + policy.setContext( logContext ); + policy.setParent( appender ); + policy.setFileNamePattern( enclosingFolder + "/archives/" + fileName + ".%d{yyyy-MM-dd}.%i.log.zip" ); + policy.setMaxHistory( 90 ); // Keep 90 days + policy.setMaxFileSize( FileSize.valueOf( "100MB" ) ); // Maximum file size for each log + policy.setTotalSizeCap( FileSize.valueOf( "5GB" ) ); // Max total cap size + policy.start(); + + // Configure it + appender.setRollingPolicy( policy ); appender.start(); + + // Uncomment to verify issues + // StatusPrinter.print( logContext ); + return appender; } ); } @@ -425,7 +457,7 @@ public LoggingService shutdownAppenders() { public LoggingService shutdown() { // Shutdown all the appenders shutdownAppenders(); - this.loggerContext.stop(); + getLoggerContext().stop(); return instance; } From 6f2090840f66f94511858f2313c6f3e5fa3a4c22 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Tue, 26 Nov 2024 11:11:34 -0500 Subject: [PATCH 21/38] fix nested BX components not writing correctly to the buffer --- .../boxlang/runtime/modules/ModuleRecord.java | 3 +- .../src/main/bx/components/HelloComponent.bx | 2 +- .../src/main/bx/components/NameComponent.bx | 62 +++++++++++++++++++ .../runtime/modules/ModuleRecordTest.java | 9 +-- 4 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 src/modules/test/src/main/bx/components/NameComponent.bx diff --git a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java index 762c74d6d..05b0a6c65 100644 --- a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java +++ b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java @@ -799,7 +799,7 @@ private ModuleRecord registerComponent( File targetFile, IBoxContext context ) { return oComponentProxy.processBody( ( IBoxContext ) args.get( Key.context ), ( ComponentBody ) args.get( Key.body ), - buffer instanceof StringBuffer ? ( StringBuffer ) buffer : new StringBuffer() + buffer instanceof StringBuffer ? ( StringBuffer ) buffer : context.getBuffer() ); }, new Argument[] { @@ -831,7 +831,6 @@ private ModuleRecord registerComponent( File targetFile, IBoxContext context ) { BooleanCaster.cast( annotations.getOrDefault( "RequiresBody", false ) ) ); Key[] componentAliases = buildAnnotationAliases( oComponent, className, Key.boxComponent ); - System.out.println( "Component Aliases: " + Arrays.toString( componentAliases ) ); // Register all components with their aliases for ( Key thisAlias : componentAliases ) { diff --git a/src/modules/test/src/main/bx/components/HelloComponent.bx b/src/modules/test/src/main/bx/components/HelloComponent.bx index 7d4aada60..0c6bb1cfe 100644 --- a/src/modules/test/src/main/bx/components/HelloComponent.bx +++ b/src/modules/test/src/main/bx/components/HelloComponent.bx @@ -52,7 +52,7 @@ class{ return bodyResult; } // // reverse the buffer contents and place into a string - var newContent = buffer.reverse().toString(); + var newContent = buffer.toString(); // // output it to the page buffer context.writeToBuffer( newContent ); } diff --git a/src/modules/test/src/main/bx/components/NameComponent.bx b/src/modules/test/src/main/bx/components/NameComponent.bx new file mode 100644 index 000000000..1996b5abe --- /dev/null +++ b/src/modules/test/src/main/bx/components/NameComponent.bx @@ -0,0 +1,62 @@ +/** + * This is a BoxLang only Component + * + * Annotations you can use on a component: + *

+ * // The alias of the Component, defaults to the name of the Class
+ * @BoxComponent 'myComponentAlias'
+ * @BoxComponent [ 'myComponentAlias', 'anotherAlias' ]
+ * @AllowsBody [boolean=false]
+ * @RequiresBody [boolean=false]
+ * 
+ * + * The runtime injects the following into the variables scope: + * - boxRuntime : BoxLangRuntime + * - log : A logger + * - functionService : The BoxLang FunctionService + * - interceptorService : The BoxLang InterceptorService + * - moduleRecord : The ModuleRecord instance + * + * The runtime also injects the following helpers into the variables scope: + * - newBuffer() : Create and return a new StringBuffer + * - newBuilder() : Create and return a new StringBuilder + * - processBody( context, body, [buffer] ) : Process the body of a component + * - getName() : Get the name of the component + */ +@BoxComponent 'NombreComponent' +@AllowsBody true +@RequiresBody false +class{ + + /** + * The execution of this Component + * + *
+	 * This is my output
+	 * 
+ * + * @param context The context of the execution (IBoxContext) + * @param attributes The attributes of the component that were passed in + * @param body The body of the component that you can pass to `processBody(context, body, [buffer])` for execution and buffer retreival + * @param executionState The execution state of the component. Each component get's one as an isolated state. + * + * @return A BodyResult instance or null for a default result return. + */ + function invoke( required context, Struct attributes, any body, Struct executionState ){ + // A buffer to capture the body output + var buffer = newBuffer(); + var bodyResult = processBody( context, body, buffer ); + + var helloComponent = context.findClosestComponent( "HelloComponent" ); + + // // If there was a return statement inside our body, we early exit now + if ( bodyResult.isEarlyExit() ) { + return bodyResult; + } + // if we are nested inside a helloComponent, we add a comma to the output + var newContent = isNull( helloComponent ) ? buffer.toString() : ", " & buffer.toString(); + // // output it to the page buffer + context.writeToBuffer( newContent ); + } + +} diff --git a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java index cb1708de2..ccd94ea74 100644 --- a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java +++ b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java @@ -219,9 +219,10 @@ void testItCanActivateAModule() throws ClassNotFoundException { // It should register components ComponentService componentService = runtime.getComponentService(); - assertThat( moduleRecord.components.size() ).isEqualTo( 1 ); - System.out.println( Arrays.toString( componentService.getComponentNames() ) ); + assertThat( moduleRecord.components.size() ).isEqualTo( 2 ); + // System.out.println( Arrays.toString( componentService.getComponentNames() ) ); assertThat( componentService.hasComponent( Key.of( "HolaComponent" ) ) ).isTrue(); + assertThat( componentService.hasComponent( Key.of( "NombreComponent" ) ) ).isTrue(); // Register a class loader Class clazz = moduleRecord.findModuleClass( "HelloWorld", false, context ); @@ -262,12 +263,12 @@ void testItCanActivateAModule() throws ClassNotFoundException { // Test the component body execution // @formatter:off runtime.executeSource(""" - Hello + Hello Luis """, context, BoxSourceType.BOXTEMPLATE ); // @formatter:on // The buffer is reversed on this component so it should be backwards - assertThat( variables.getAsString( Key.of( "result4" ) ).trim() ).isEqualTo( "olleH" ); + assertThat( variables.getAsString( Key.of( "result4" ) ).trim() ).isEqualTo( "Hello , Luis" ); // Test Module Class Locators // @formatter:off From a7848d7ab841d07f4210f19907f6949e0c5e0956 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 26 Nov 2024 23:15:44 +0100 Subject: [PATCH 22/38] BL-793 #resolve Add support for logging configuration items --- .../ortus/boxlang/runtime/BoxRuntime.java | 7 +- .../boxlang/runtime/config/Configuration.java | 18 ++- .../config/segments/LoggingConfig.java | 147 ++++++++++++++++++ .../runtime/logging/LoggingService.java | 91 ++++++++--- .../ortus/boxlang/runtime/scopes/Key.java | 6 + src/main/resources/config/boxlang.json | 27 +++- .../bifs/global/system/WriteLogTest.java | 2 +- .../runtime/components/system/LogTest.java | 2 +- .../runtime/config/ConfigLoaderTest.java | 4 +- .../interceptors/LoggingInterceptorTest.java | 2 +- 10 files changed, 267 insertions(+), 39 deletions(-) create mode 100644 src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index 1f3c19620..9d03cca8a 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -357,13 +357,12 @@ private void loadConfiguration( Boolean debugMode, String configPath ) { // Finally verify if we overwrote the debugmode in one of the configs above if ( debugMode == null ) { this.debugMode = this.configuration.debugMode; - // Reconfigure the logging if enabled - if ( this.debugMode ) { - this.loggingService.reconfigureDebugMode( this.debugMode ); - } this.logger.info( "+ DebugMode detected in config, overriding to {}", this.debugMode ); } + // Reconfigure the logging services + this.loggingService.reconfigure(); + // AST Capture experimental feature BooleanCaster.attempt( this.configuration.experimental.getOrDefault( "ASTCapture", false ) ).ifSuccessful( diff --git a/src/main/java/ortus/boxlang/runtime/config/Configuration.java b/src/main/java/ortus/boxlang/runtime/config/Configuration.java index e1172fdcf..a0ae2e536 100644 --- a/src/main/java/ortus/boxlang/runtime/config/Configuration.java +++ b/src/main/java/ortus/boxlang/runtime/config/Configuration.java @@ -40,6 +40,7 @@ import ortus.boxlang.runtime.config.segments.DatasourceConfig; import ortus.boxlang.runtime.config.segments.ExecutorConfig; import ortus.boxlang.runtime.config.segments.IConfigSegment; +import ortus.boxlang.runtime.config.segments.LoggingConfig; import ortus.boxlang.runtime.config.segments.ModuleConfig; import ortus.boxlang.runtime.config.segments.SecurityConfig; import ortus.boxlang.runtime.config.util.PlaceholderHelper; @@ -179,12 +180,6 @@ public class Configuration implements IConfigSegment { public List modulesDirectory = new ArrayList<>( Arrays.asList( BoxRuntime.getInstance().getRuntimeHome().toString() + "/modules" ) ); - /** - * The default logs directory for the runtime - */ - public String logsDirectory = Paths.get( BoxRuntime.getInstance().getRuntimeHome().toString(), "/logs" ).normalize() - .toString(); - /** * An array of directories where custom tags are located and loaded from. * {@code [ /{boxlang-home}/customTags ]} @@ -260,6 +255,11 @@ public class Configuration implements IConfigSegment { */ public SecurityConfig security = new SecurityConfig(); + /** + * The logging configuration + */ + public LoggingConfig logging = new LoggingConfig(); + /** * -------------------------------------------------------------------------- * Private Properties @@ -583,6 +583,11 @@ public Configuration process( IStruct config ) { security.process( StructCaster.cast( config.get( Key.security ) ) ); } + // Process our logging configuration + if ( config.containsKey( Key.logging ) ) { + logging.process( StructCaster.cast( config.get( Key.logging ) ) ); + } + return this; } @@ -868,6 +873,7 @@ public IStruct asStruct() { Key.whitespaceCompressionEnabled, this.whitespaceCompressionEnabled, Key.javaLibraryPaths, Array.fromList( this.javaLibraryPaths ), Key.locale, this.locale, + Key.logging, this.logging.asStruct(), Key.mappings, mappingsCopy, Key.modules, modulesCopy, Key.modulesDirectory, Array.fromList( this.modulesDirectory ), diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java new file mode 100644 index 000000000..3f3464644 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java @@ -0,0 +1,147 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ortus.boxlang.runtime.config.segments; + +import java.nio.file.Paths; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.config.util.PlaceholderHelper; +import ortus.boxlang.runtime.dynamic.casters.IntegerCaster; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.Struct; + +/** + * The BoxLang LoggingConfig class is a configuration segment that is used to define the logging settings for the BoxLang runtime. + */ +public class LoggingConfig implements IConfigSegment { + + /** + * The default logs directory for the runtime + */ + public String logsDirectory = Paths.get( BoxRuntime.getInstance().getRuntimeHome().toString(), "/logs" ) + .normalize() + .toString(); + + /** + * The maximum number of days to keep log files before rotation + * Default is 90 days or 3 months + * Set to 0 to disable + */ + public int maxLogDays = 90; + + /** + * The maximum file size for a single log file before rotation + * You can use the following suffixes: KB, MB, GB + * Default is 100MB + */ + public String maxFileSize = "100MB"; + + /** + * The total cap size of all log files before rotation + * You can use the following suffixes: KB, MB, GB + * Default is 5GB + */ + public String totalCapSize = "5GB"; + + /** + * The root logger level + */ + public String rootLevel = "INFO"; + + /** + * The collection of loggers and their levels + */ + public IStruct loggers = Struct.of(); + + /** + * -------------------------------------------------------------------------- + * Private Props + * -------------------------------------------------------------------------- + */ + + /** + * Logger + */ + private static final Logger logger = LoggerFactory.getLogger( LoggingConfig.class ); + + /** + * -------------------------------------------------------------------------- + * Methods + * -------------------------------------------------------------------------- + */ + + /** + * Default empty constructor + */ + public LoggingConfig() { + // Default all things + } + + /** + * Processes the configuration struct. Each segment is processed individually from the initial configuration struct. + * + * @param config the configuration struct + * + * @return the configuration + */ + @Override + public IConfigSegment process( IStruct config ) { + + // Debug Mode || Debbuging Enabled (cfconfig) + if ( config.containsKey( Key.logsDirectory ) ) { + this.logsDirectory = PlaceholderHelper.resolve( config.get( Key.logsDirectory ) ); + } + + if ( config.containsKey( Key.maxLogDays ) ) { + this.maxLogDays = IntegerCaster.cast( PlaceholderHelper.resolve( config.get( Key.maxLogDays ) ) ); + } + + if ( config.containsKey( Key.maxFileSize ) ) { + this.maxFileSize = PlaceholderHelper.resolve( config.get( Key.maxFileSize ) ); + } + + if ( config.containsKey( Key.totalCapSize ) ) { + this.totalCapSize = PlaceholderHelper.resolve( config.get( Key.totalCapSize ) ); + } + + if ( config.containsKey( Key.rootLevel ) ) { + this.rootLevel = PlaceholderHelper.resolve( config.get( Key.rootLevel ) ); + } + + return this; + } + + /** + * @inheritDoc + */ + @Override + public IStruct asStruct() { + return Struct.of( + Key.logsDirectory, this.logsDirectory, + Key.maxLogDays, this.maxLogDays, + Key.maxFileSize, this.maxFileSize, + Key.rootLevel, this.rootLevel, + Key.totalCapSize, this.totalCapSize + ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index dcb94fa36..d76f7c62e 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -62,17 +62,17 @@ public class LoggingService { * -------------------------------------------------------------------------- */ - public static final String DEFAULT_LOG_LEVEL = "info"; - public static final String DEFAULT_LOG_TYPE = "Application"; - public static final String DEFAULT_LOG_CATEGORY = "boxruntime"; - public static final String CONTEXT_NAME = "BoxLang"; + public static final String DEFAULT_LOG_LEVEL = "info"; + public static final String DEFAULT_LOG_TYPE = "Application"; + public static final String DEFAULT_LOG_FILE = "boxruntime"; + public static final String CONTEXT_NAME = "BoxLang"; /** * The log format for the BoxLang runtime * * @see https://logback.qos.ch/manual/layouts.html#conversionWord */ - public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message %ex%n"; + public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message %ex%n"; /** * -------------------------------------------------------------------------- @@ -108,7 +108,7 @@ public class LoggingService { /** * A map of registered appenders */ - private Map> appendersMap = new ConcurrentHashMap<>(); + private Map> appendersMap = new ConcurrentHashMap<>(); /** * -------------------------------------------------------------------------- @@ -271,17 +271,61 @@ public LoggingService setLoggerContext( LoggerContext loggerContext ) { */ /** - * Enable debug mode for the runtime's root logger or not. + * This method is called by the runtime to reconfigure the logging system + * once the configuration file has been read. + *

+ * This could change logging levels, add new appenders, etc. + */ + public LoggingService reconfigure() { + + // Setup the runtime appender + FileAppender runtimeAppender = getOrBuildAppender( + getLogsDirectory() + "/boxruntime.log", + this.loggerContext + ); + + // Reconfigure Root Logger + Level rootLevel = Level.toLevel( this.runtime.getConfiguration().logging.rootLevel ); + this.rootLogger.setLevel( rootLevel ); + this.rootLogger.addAppender( runtimeAppender ); + + return instance; + } + + /** + * Log a message into the default log file and the default log type * - * This is usually a convenience method for the runtime to enable or disable - * debug mode - * via configuration overrides + * @param message The message to log * - * @param debugMode True to enable debug mode, false to disable + * @return The logging service */ - public LoggingService reconfigureDebugMode( Boolean debugMode ) { - this.rootLogger.setLevel( Boolean.TRUE.equals( debugMode ) ? Level.DEBUG : Level.INFO ); - return instance; + public LoggingService logMessage( String message ) { + return logMessage( message, DEFAULT_LOG_LEVEL, "no-application", DEFAULT_LOG_FILE ); + } + + /** + * Log a message into the default log file and a custom log type + * + * @param message The message to log + * @param type The type of log message (fatal, error, info, warn, debug, trace) + * + * @return The logging service + */ + public LoggingService logMessage( String message, String type ) { + return logMessage( message, type, "no-application", DEFAULT_LOG_FILE ); + } + + /** + * Log a message into the default log file and a custom log type + * + * @param message The message to log + * @param type The type of log message (fatal, error, info, warn, debug, trace) + * @param applicationName The name of the application requesting the log message + * + * @return The logging service + */ + public LoggingService logMessage( String message, String type, String applicationName ) { + return logMessage( message, type, applicationName, DEFAULT_LOG_FILE ); } /** @@ -317,7 +361,7 @@ public LoggingService logMessage( // If no file or log is passed, then use the default log file: boxruntime.log if ( logFile.isEmpty() ) { - logFile = LoggingService.DEFAULT_LOG_CATEGORY + ".log"; + logFile = LoggingService.DEFAULT_LOG_FILE + ".log"; } // Verify the log file ends in `.log` and if not, append it @@ -362,7 +406,7 @@ public LoggingService logMessage( * Get the runtime's log directory as per the configuration */ public String getLogsDirectory() { - return this.runtime.getConfiguration().logsDirectory; + return this.runtime.getConfiguration().logging.logsDirectory; } /** @@ -381,7 +425,7 @@ public FileAppender getOrBuildAppender( String filePath, LoggerCo String enclosingFolder = FilenameUtils.getFullPathNoEndSeparator( filePath ); // Set basics of appender - appender.setName( "bx." + fileName ); + appender.setName( fileName ); appender.setFile( filePath ); appender.setContext( logContext ); appender.setEncoder( getEncoder() ); @@ -391,14 +435,19 @@ public FileAppender getOrBuildAppender( String filePath, LoggerCo // appender.setPrudent( true ); // Time-based rolling policy with file size constraint - // TODO: Get from configuration items SizeAndTimeBasedRollingPolicy policy = new SizeAndTimeBasedRollingPolicy<>(); policy.setContext( logContext ); policy.setParent( appender ); policy.setFileNamePattern( enclosingFolder + "/archives/" + fileName + ".%d{yyyy-MM-dd}.%i.log.zip" ); - policy.setMaxHistory( 90 ); // Keep 90 days - policy.setMaxFileSize( FileSize.valueOf( "100MB" ) ); // Maximum file size for each log - policy.setTotalSizeCap( FileSize.valueOf( "5GB" ) ); // Max total cap size + policy.setMaxHistory( + this.runtime.getConfiguration().logging.maxLogDays + ); + policy.setMaxFileSize( FileSize.valueOf( + this.runtime.getConfiguration().logging.maxFileSize + ) ); // Maximum file size for each log + policy.setTotalSizeCap( FileSize.valueOf( + this.runtime.getConfiguration().logging.totalCapSize + ) ); // Max total cap size policy.start(); // Configure it diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index 14564325f..3562aae2e 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -424,6 +424,8 @@ public class Key implements Comparable, Serializable { public static final Key listToJSON = Key.of( "listToJSON" ); public static final Key lJustify = Key.of( "lJustify" ); public static final Key loadPaths = Key.of( "loadPaths" ); + public static final Key logging = Key.of( "logging" ); + public static final Key logsDirectory = Key.of( "logsDirectory" ); public static final Key loggingService = Key.of( "loggingService" ); public static final Key local_addr = Key.of( "local_addr" ); public static final Key local_host = Key.of( "local_host" ); @@ -439,6 +441,8 @@ public class Key implements Comparable, Serializable { public static final Key match = Key.of( "match" ); public static final Key max = Key.of( "max" ); public static final Key maxFrames = Key.of( "maxFrames" ); + public static final Key maxFileSize = Key.of( "maxFileSize" ); + public static final Key maxLogDays = Key.of( "maxLogDays" ); public static final Key maxLength = Key.of( "maxLength" ); public static final Key maxObjects = Key.of( "maxObjects" ); public static final Key maxRows = Key.of( "maxRows" ); @@ -562,6 +566,7 @@ public class Key implements Comparable, Serializable { public static final Key recordCount = Key.of( "recordCount" ); public static final Key reFindNoCase = Key.of( "reFindNoCase" ); public static final Key recurse = Key.of( "recurse" ); + public static final Key rootLevel = Key.of( "rootLevel" ); public static final Key recursive = Key.of( "recursive" ); public static final Key redirect = Key.of( "redirect" ); public static final Key reg_expression = Key.of( "reg_expression" ); @@ -702,6 +707,7 @@ public class Key implements Comparable, Serializable { public static final Key token = Key.of( "token" ); public static final Key toKey = Key.of( "toKey" ); public static final Key top = Key.of( "top" ); + public static final Key totalCapSize = Key.of( "totalCapSize" ); public static final Key trim = Key.of( "trim" ); public static final Key type = Key.of( "type" ); public static final Key typename = Key.of( "typename" ); diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index f69c27c81..a0e9008cb 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -72,8 +72,29 @@ "javaLibraryPaths": [ "${boxlang-home}/lib" ], - // The location of the log files the runtime will produce - "logsDirectory": "${boxlang-home}/logs", + // Logging Settings for the runtime + "logging": { + // The location of the log files the runtime will produce + "logsDirectory": "${boxlang-home}/logs", + // The maximum number of days to keep log files before rotation + // Default is 90 days or 3 months + // Set to 0 to never rotate + "maxLogDays": 90, + // The maximum file size for a single log file before rotation + // You can use the following suffixes: KB, MB, GB + // Default is 100MB + "maxFileSize": "100MB", + // The total cap size of all log files before rotation + // You can use the following suffixes: KB, MB, GB + // Default is 5GB + "totalCapSize": "5GB", + // The root logger level + // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF + // If the runtime is in Debug mode, this will be set to DEBUG + "rootLevel": "INFO", + // A collection of loggers and their configurations + "loggers": {} + }, // This is the experimental features flags. // Please see the documentation to see which flags are available "experimental": { @@ -313,4 +334,4 @@ // } // } } -} \ No newline at end of file +} diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java index b27354028..7c21425b5 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java @@ -57,7 +57,7 @@ public class WriteLogTest { @BeforeAll public static void setUp() { instance = BoxRuntime.getInstance( true ); - logsDirectory = instance.getConfiguration().logsDirectory; + logsDirectory = instance.getConfiguration().logging.logsDirectory; outContent = new ByteArrayOutputStream(); System.setOut( new PrintStream( outContent ) ); logFilePath = Paths.get( logsDirectory, "/writelog.log" ).normalize().toString(); diff --git a/src/test/java/ortus/boxlang/runtime/components/system/LogTest.java b/src/test/java/ortus/boxlang/runtime/components/system/LogTest.java index 8f9075b7c..1731fffe3 100644 --- a/src/test/java/ortus/boxlang/runtime/components/system/LogTest.java +++ b/src/test/java/ortus/boxlang/runtime/components/system/LogTest.java @@ -57,7 +57,7 @@ public class LogTest { @BeforeAll public static void setUp() { instance = BoxRuntime.getInstance( true ); - logsDirectory = instance.getConfiguration().logsDirectory; + logsDirectory = instance.getConfiguration().logging.logsDirectory; outContent = new ByteArrayOutputStream(); System.setOut( new PrintStream( outContent ) ); logFileName = "bxlog.log"; diff --git a/src/test/java/ortus/boxlang/runtime/config/ConfigLoaderTest.java b/src/test/java/ortus/boxlang/runtime/config/ConfigLoaderTest.java index 39b028ee5..43eb8798c 100644 --- a/src/test/java/ortus/boxlang/runtime/config/ConfigLoaderTest.java +++ b/src/test/java/ortus/boxlang/runtime/config/ConfigLoaderTest.java @@ -72,7 +72,7 @@ void testItCanLoadTheCoreConfig() { assertThat( config.modulesDirectory.get( 0 ) ).doesNotContainMatch( "(ignorecase)\\{boxlang-home\\}" ); // Log Directory Check - assertThat( config.logsDirectory ).isNotEmpty(); + assertThat( config.logging.logsDirectory ).isNotEmpty(); // Cache Checks assertThat( config.caches ).isNotEmpty(); @@ -225,7 +225,7 @@ void testItCanMergeEnvironmentalProperties() { assertThat( config.modulesDirectory.get( 0 ) ).doesNotContainMatch( "(ignorecase)\\{boxlang-home\\}" ); // Log Directory Check - assertThat( config.logsDirectory ).isNotEmpty(); + assertThat( config.logging.logsDirectory ).isNotEmpty(); // Cache Checks assertThat( config.caches ).isNotEmpty(); diff --git a/src/test/java/ortus/boxlang/runtime/interceptors/LoggingInterceptorTest.java b/src/test/java/ortus/boxlang/runtime/interceptors/LoggingInterceptorTest.java index d94e88820..9a03e0342 100644 --- a/src/test/java/ortus/boxlang/runtime/interceptors/LoggingInterceptorTest.java +++ b/src/test/java/ortus/boxlang/runtime/interceptors/LoggingInterceptorTest.java @@ -52,7 +52,7 @@ public class LoggingInterceptorTest { public static void setUp() { instance = BoxRuntime.getInstance( true ); loggingInterceptor = new Logging( instance ); - logDirectory = instance.getConfiguration().logsDirectory; + logDirectory = instance.getConfiguration().logging.logsDirectory; logFilePath = Paths.get( logDirectory, "/", testLogFile ).toString(); absoluteLogeFilePath = Paths.get( tmpDirectory, testLogFile ).toAbsolutePath().toString(); } From 27e7dbd19e7570a01910305999ee6d59bf4390ae Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 26 Nov 2024 23:45:21 +0100 Subject: [PATCH 23/38] fixed regressions and double logging --- .../boxlang/runtime/logging/LoggingService.java | 16 +++++----------- .../runtime/bifs/global/system/WriteLogTest.java | 8 +++++++- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index d76f7c62e..1bcdf2930 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -64,7 +64,7 @@ public class LoggingService { public static final String DEFAULT_LOG_LEVEL = "info"; public static final String DEFAULT_LOG_TYPE = "Application"; - public static final String DEFAULT_LOG_FILE = "boxruntime"; + public static final String DEFAULT_LOG_FILE = "boxruntime.log"; public static final String CONTEXT_NAME = "BoxLang"; /** @@ -156,6 +156,8 @@ public LoggingService configureBasic( Boolean debugMode ) { this.loggerContext = new LoggerContext(); } + this.loggerContext.reset(); + // Name it this.loggerContext.setName( CONTEXT_NAME ); @@ -277,17 +279,9 @@ public LoggingService setLoggerContext( LoggerContext loggerContext ) { * This could change logging levels, add new appenders, etc. */ public LoggingService reconfigure() { - - // Setup the runtime appender - FileAppender runtimeAppender = getOrBuildAppender( - getLogsDirectory() + "/boxruntime.log", - this.loggerContext - ); - // Reconfigure Root Logger - Level rootLevel = Level.toLevel( this.runtime.getConfiguration().logging.rootLevel ); + Level rootLevel = Level.toLevel( this.runtime.getConfiguration().logging.rootLevel ); this.rootLogger.setLevel( rootLevel ); - this.rootLogger.addAppender( runtimeAppender ); return instance; } @@ -361,7 +355,7 @@ public LoggingService logMessage( // If no file or log is passed, then use the default log file: boxruntime.log if ( logFile.isEmpty() ) { - logFile = LoggingService.DEFAULT_LOG_FILE + ".log"; + logFile = DEFAULT_LOG_FILE; } // Verify the log file ends in `.log` and if not, append it diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java index 7c21425b5..6b3a704cf 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java @@ -73,7 +73,13 @@ public static void teardown() { System.setOut( originalOut ); LoggingService.getInstance().shutdownAppenders(); if ( FileSystemUtil.exists( logFilePath ) ) { - FileSystemUtil.deleteFile( logFilePath ); + try { + FileSystemUtil.deleteFile( logFilePath ); + } catch ( Exception e ) { + // Leave this due to stupid windows file locking + // We can't use prudent also due to rolling file appenders + e.printStackTrace(); + } } } From ef38ce95c707f17e6c97e1022b1e3ebdd45dbaa3 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 26 Nov 2024 23:46:15 +0100 Subject: [PATCH 24/38] default level will be warn --- src/main/resources/config/boxlang.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index a0e9008cb..7adf0c253 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -91,7 +91,7 @@ // The root logger level // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // If the runtime is in Debug mode, this will be set to DEBUG - "rootLevel": "INFO", + "rootLevel": "WARN", // A collection of loggers and their configurations "loggers": {} }, From 4579e6dcab29fb6c5a71889e3c819028fd9b51c6 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Wed, 27 Nov 2024 10:37:41 -0500 Subject: [PATCH 25/38] BL-795 Resolve - getColumnList and getColumnArray to columnList and columnArray --- .../runtime/bifs/global/query/QueryColumnArray.java | 2 +- .../ortus/boxlang/runtime/context/BaseBoxContext.java | 2 +- src/main/java/ortus/boxlang/runtime/types/Query.java | 11 +++++++---- .../runtime/bifs/global/system/DuplicateTest.java | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnArray.java b/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnArray.java index b7a4b1c8f..09a3136a1 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnArray.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnArray.java @@ -46,6 +46,6 @@ public QueryColumnArray() { * @argument.query The query to get the column names from */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - return arguments.getAsQuery( Key.query ).getColumnArray(); + return arguments.getAsQuery( Key.query ).columnArray(); } } diff --git a/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java index 40060f20b..92f19ba89 100644 --- a/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java @@ -760,7 +760,7 @@ protected ScopeSearchResult queryFindNearby( Key key ) { return new ScopeSearchResult( null, queryLoops.get( query ) + 1, key ); } if ( key.equals( Key.columnList ) ) { - return new ScopeSearchResult( null, query.getColumnList(), key ); + return new ScopeSearchResult( null, query.columnList(), key ); } if ( query.hasColumn( key ) ) { // TODO: create query scope wrapper for edge cases diff --git a/src/main/java/ortus/boxlang/runtime/types/Query.java b/src/main/java/ortus/boxlang/runtime/types/Query.java index c7238506f..1a38f267b 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Query.java +++ b/src/main/java/ortus/boxlang/runtime/types/Query.java @@ -36,6 +36,7 @@ import java.time.Duration; import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.bifs.BoxMemberExpose; import ortus.boxlang.runtime.bifs.MemberDescriptor; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.IReferenceable; @@ -645,7 +646,8 @@ public int getRowFromContext( IBoxContext context ) { * * @return column names as string */ - public String getColumnList() { + @BoxMemberExpose + public String columnList() { return getColumns().keySet().stream().map( Key::getName ).collect( Collectors.joining( "," ) ); } @@ -654,7 +656,8 @@ public String getColumnList() { * * @return column names as array */ - public Array getColumnArray() { + @BoxMemberExpose + public Array columnArray() { return getColumns().keySet().stream().map( Key::getName ).collect( BLCollector.toArray() ); } @@ -790,7 +793,7 @@ public Object dereference( IBoxContext context, Key name, Boolean safe ) { return size(); } if ( name.equals( Key.columnList ) ) { - return getColumnList(); + return columnList(); } if ( name.equals( Key.currentRow ) ) { return getRowFromContext( context ) + 1; @@ -881,7 +884,7 @@ public IStruct getMetaData() { this.metadata.computeIfAbsent( Key.cacheLastAccessTimeout, key -> Duration.ZERO ); this.metadata.computeIfAbsent( Key.recordCount, key -> data.size() ); this.metadata.computeIfAbsent( Key.columns, key -> this.getColumns() ); - this.metadata.computeIfAbsent( Key.columnList, key -> this.getColumnList() ); + this.metadata.computeIfAbsent( Key.columnList, key -> this.columnList() ); this.metadata.computeIfAbsent( Key._HASHCODE, key -> this.hashCode() ); return this.metadata; } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/DuplicateTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/DuplicateTest.java index f7ec7b19a..db3fa5da7 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/DuplicateTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/DuplicateTest.java @@ -409,7 +409,7 @@ public void testDuplicateQuery() { Query result = QueryCaster.cast( variables.get( resultKey ) ); assertEquals( ref.size(), result.size() ); - assertEquals( ref.getColumnList(), result.getColumnList() ); + assertEquals( ref.columnList(), result.columnList() ); for ( int i = 0; i < ref.size(); i++ ) { for ( Key columnName : ref.getColumns().keySet() ) { QueryColumn a = ref.getColumn( columnName ); From 99c2d619d3dfb1528c792bae21f609a5ae908367 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 27 Nov 2024 18:26:17 +0100 Subject: [PATCH 26/38] add status printer for debug mode --- .../java/ortus/boxlang/runtime/logging/LoggingService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 1bcdf2930..d88b6e82b 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -39,6 +39,7 @@ import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; import ch.qos.logback.core.util.FileSize; +import ch.qos.logback.core.util.StatusPrinter; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.scopes.Key; @@ -449,7 +450,9 @@ public FileAppender getOrBuildAppender( String filePath, LoggerCo appender.start(); // Uncomment to verify issues - // StatusPrinter.print( logContext ); + if ( this.runtime.inDebugMode() ) { + StatusPrinter.print( logContext ); + } return appender; } ); From 4444ede2c3cc0d232615a117ad6bba2186993794 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 27 Nov 2024 19:20:48 +0100 Subject: [PATCH 27/38] trying basic mdc adapter for logger context --- .../runtime/logging/LoggingService.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index d88b6e82b..943f5d9da 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -33,6 +33,7 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.util.LogbackMDCAdapterSimple; import ch.qos.logback.core.Appender; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.FileAppender; @@ -44,16 +45,25 @@ import ortus.boxlang.runtime.scopes.Key; /** - * This service allows BoxLang to leverage logging facilities and interact with the logging system. + * This service allows BoxLang to leverage logging facilities and interact with the logging system: LogBack: https://logback.qos.ch/manual/index.html *

* It also manages all custom logging events, appenders and loggers. *

* It's not a true BoxLang service, due to the chicken and egg problem of logging being needed before the runtime starts. *

- * The configureBasic() method is called by the runtime to setup the basic logging system. + * The {@link #configureBasic(Boolean)} method is called by the runtime to setup the basic logging system first, then + * once the runtime is online and has read the configuration file (boxlang.json), it can reconfigure the logging system + * via the {@link #reconfigure()} method. *

- * Once the runtime is online and has read the configuration file (boxlang.json), it can reconfigure the logging system. - * This could change logging levels, add new appenders, etc. + * Please note that in BoxLang you can use the following arguments for logging via the {@link #logMessage(String, String, String, String)} method: + *

    + *
  • message - The message to send for logging or a lambda that produces the message
  • + *
  • type - The logging level type (error, info, warn, debug, trace)
  • + *
  • applicationName - The name of the BoxLang application (if any)
  • + *
  • logger - The named logger to emit to. Example: "scheduler, application, orm, etc"
  • + *
+ *

+ * If the named logger does not exist or it's an absolute path, then the logger will be registered as a new logger, with the name of the file as the category. */ public class LoggingService { @@ -158,6 +168,7 @@ public LoggingService configureBasic( Boolean debugMode ) { } this.loggerContext.reset(); + this.loggerContext.setMDCAdapter( new LogbackMDCAdapterSimple() ); // Name it this.loggerContext.setName( CONTEXT_NAME ); From df4e70a22a501801c20bd33df8a3293c70985c07 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Wed, 27 Nov 2024 13:16:53 -0500 Subject: [PATCH 28/38] Revert "BL-795 Resolve - getColumnList and getColumnArray to columnList and columnArray" This reverts commit 4579e6dcab29fb6c5a71889e3c819028fd9b51c6. --- .../runtime/bifs/global/query/QueryColumnArray.java | 2 +- .../ortus/boxlang/runtime/context/BaseBoxContext.java | 2 +- src/main/java/ortus/boxlang/runtime/types/Query.java | 11 ++++------- .../runtime/bifs/global/system/DuplicateTest.java | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnArray.java b/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnArray.java index 09a3136a1..b7a4b1c8f 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnArray.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnArray.java @@ -46,6 +46,6 @@ public QueryColumnArray() { * @argument.query The query to get the column names from */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - return arguments.getAsQuery( Key.query ).columnArray(); + return arguments.getAsQuery( Key.query ).getColumnArray(); } } diff --git a/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java index 92f19ba89..40060f20b 100644 --- a/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java @@ -760,7 +760,7 @@ protected ScopeSearchResult queryFindNearby( Key key ) { return new ScopeSearchResult( null, queryLoops.get( query ) + 1, key ); } if ( key.equals( Key.columnList ) ) { - return new ScopeSearchResult( null, query.columnList(), key ); + return new ScopeSearchResult( null, query.getColumnList(), key ); } if ( query.hasColumn( key ) ) { // TODO: create query scope wrapper for edge cases diff --git a/src/main/java/ortus/boxlang/runtime/types/Query.java b/src/main/java/ortus/boxlang/runtime/types/Query.java index 1a38f267b..c7238506f 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Query.java +++ b/src/main/java/ortus/boxlang/runtime/types/Query.java @@ -36,7 +36,6 @@ import java.time.Duration; import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.bifs.BoxMemberExpose; import ortus.boxlang.runtime.bifs.MemberDescriptor; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.IReferenceable; @@ -646,8 +645,7 @@ public int getRowFromContext( IBoxContext context ) { * * @return column names as string */ - @BoxMemberExpose - public String columnList() { + public String getColumnList() { return getColumns().keySet().stream().map( Key::getName ).collect( Collectors.joining( "," ) ); } @@ -656,8 +654,7 @@ public String columnList() { * * @return column names as array */ - @BoxMemberExpose - public Array columnArray() { + public Array getColumnArray() { return getColumns().keySet().stream().map( Key::getName ).collect( BLCollector.toArray() ); } @@ -793,7 +790,7 @@ public Object dereference( IBoxContext context, Key name, Boolean safe ) { return size(); } if ( name.equals( Key.columnList ) ) { - return columnList(); + return getColumnList(); } if ( name.equals( Key.currentRow ) ) { return getRowFromContext( context ) + 1; @@ -884,7 +881,7 @@ public IStruct getMetaData() { this.metadata.computeIfAbsent( Key.cacheLastAccessTimeout, key -> Duration.ZERO ); this.metadata.computeIfAbsent( Key.recordCount, key -> data.size() ); this.metadata.computeIfAbsent( Key.columns, key -> this.getColumns() ); - this.metadata.computeIfAbsent( Key.columnList, key -> this.columnList() ); + this.metadata.computeIfAbsent( Key.columnList, key -> this.getColumnList() ); this.metadata.computeIfAbsent( Key._HASHCODE, key -> this.hashCode() ); return this.metadata; } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/DuplicateTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/DuplicateTest.java index db3fa5da7..f7ec7b19a 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/DuplicateTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/DuplicateTest.java @@ -409,7 +409,7 @@ public void testDuplicateQuery() { Query result = QueryCaster.cast( variables.get( resultKey ) ); assertEquals( ref.size(), result.size() ); - assertEquals( ref.columnList(), result.columnList() ); + assertEquals( ref.getColumnList(), result.getColumnList() ); for ( int i = 0; i < ref.size(); i++ ) { for ( Key columnName : ref.getColumns().keySet() ) { QueryColumn a = ref.getColumn( columnName ); From d3cc5f0823e556c7e55eacd540ee515047b1913c Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Wed, 27 Nov 2024 18:22:57 -0500 Subject: [PATCH 29/38] BL-795 undo class method changes and implement QueryColumnListBIF and member --- .../bifs/global/query/QueryColumnList.java | 52 ++++++++++ .../global/query/QueryColumnListTest.java | 95 +++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnList.java create mode 100644 src/test/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnListTest.java diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnList.java b/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnList.java new file mode 100644 index 000000000..921fd8690 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnList.java @@ -0,0 +1,52 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package ortus.boxlang.runtime.bifs.global.query; + +import ortus.boxlang.runtime.bifs.BIF; +import ortus.boxlang.runtime.bifs.BoxBIF; +import ortus.boxlang.runtime.bifs.BoxMember; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.scopes.ArgumentsScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.Argument; +import ortus.boxlang.runtime.types.BoxLangType; +import ortus.boxlang.runtime.types.util.ListUtil; + +@BoxBIF +@BoxMember( type = BoxLangType.QUERY ) +public class QueryColumnList extends BIF { + + /** + * Constructor + */ + public QueryColumnList() { + super(); + declaredArguments = new Argument[] { + new Argument( true, "query", Key.query ) + }; + } + + /** + * This function returns the delimited column list of a query. + * + * @param context The context in which the BIF is being invoked. + * @param arguments Argument scope for the BIF. + * + * @argument.query The query to get the column names from + */ + public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { + return arguments.getAsQuery( Key.query ).getColumnList(); + } +} diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnListTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnListTest.java new file mode 100644 index 000000000..86aef3ebc --- /dev/null +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/query/QueryColumnListTest.java @@ -0,0 +1,95 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ortus.boxlang.runtime.bifs.global.query; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.Array; +import ortus.boxlang.runtime.types.util.ListUtil; + +public class QueryColumnListTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + } + + @DisplayName( "It should return an list of column names" ) + @Test + public void testQueryColumnList() { + instance.executeSource( + """ + query = QueryNew( "id,name", "integer,varchar" ); + result = QueryColumnList( query ); + """, + context ); + + assertTrue( variables.get( result ) instanceof String ); + Array arrayResult = ListUtil.asList( variables.getAsString( result ), "," ); + assertThat( arrayResult.size() ).isEqualTo( 2 ); + assertThat( arrayResult.get( 0 ) ).isEqualTo( "id" ); + assertThat( arrayResult.get( 1 ) ).isEqualTo( "name" ); + } + + @DisplayName( "It should work with member functions" ) + @Test + public void testQueryColumnListMemberFunction() { + instance.executeSource( + """ + query = QueryNew( "id,name", "integer,varchar" ); + result = query.ColumnList(); + """, + context ); + + assertTrue( variables.get( result ) instanceof String ); + Array arrayResult = ListUtil.asList( variables.getAsString( result ), "," ); + assertThat( arrayResult.size() ).isEqualTo( 2 ); + assertThat( arrayResult.get( 0 ) ).isEqualTo( "id" ); + assertThat( arrayResult.get( 1 ) ).isEqualTo( "name" ); + } +} From 2094f373aa7ec65e3ba826b06f6816806568609f Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 27 Nov 2024 18:49:56 -0600 Subject: [PATCH 30/38] BL-797 BL-798 BL-799 --- src/main/antlr/BoxTemplateLexer.g4 | 2 + src/main/antlr/CFLexer.g4 | 33 ++++- .../compiler/parser/BoxScriptLexerCustom.java | 61 ++++++--- .../compiler/parser/CFLexerCustom.java | 83 ++++++++++-- .../boxlang/compiler/parser/CFParser.java | 5 + .../TestCases/components/BoxTemplateTest.java | 24 ++++ .../TestCases/components/CFTemplateTest.java | 24 ++++ .../java/TestCases/phase1/CoreLangTest.java | 118 ++++++++++++++++-- 8 files changed, 305 insertions(+), 45 deletions(-) diff --git a/src/main/antlr/BoxTemplateLexer.g4 b/src/main/antlr/BoxTemplateLexer.g4 index 0a18a899e..d4ab4d571 100644 --- a/src/main/antlr/BoxTemplateLexer.g4 +++ b/src/main/antlr/BoxTemplateLexer.g4 @@ -409,6 +409,8 @@ mode POSSIBLE_COMPONENT; PREFIX: 'bx:' -> pushMode(COMPONENT_NAME_MODE); SLASH_PREFIX: '/bx:' -> pushMode(END_COMPONENT); +COMPONENT_OPEN2: '<' -> type(COMPONENT_OPEN); + ICHAR7: '#' {_modeStack.contains(OUTPUT_MODE)}? -> type(ICHAR), popMode, pushMode(EXPRESSION_MODE_STRING ); diff --git a/src/main/antlr/CFLexer.g4 b/src/main/antlr/CFLexer.g4 index abf4a7ecb..97ec0e9c3 100644 --- a/src/main/antlr/CFLexer.g4 +++ b/src/main/antlr/CFLexer.g4 @@ -23,6 +23,7 @@ options { private int parenCount = 0; private int braceCount = 0; private int bracketCount = 0; + private boolean isQuery = false; private int countModes(int mode) { int count = 0; @@ -151,6 +152,12 @@ COMPONENT_CLOSE99: popMode, popMode ; +// We have to capture this scenario and break it apart in our lexer super class into two tokens. If we don't capture this here, the >= rule below will match it and +// fail to end the component for us, causing an invalid parse +COMPONENT_CLOSE_EQUAL: + '>=' {isExpressionComplete() && _modeStack.contains(TEMPLATE_EXPRESSION_MODE_COMPONENT)}? -> popMode, popMode, popMode, popMode, popMode +; + // Comments can live inside script expressions, if we're inside a component/tag (CF/Lucee compat only) COMMENT_START10: '