From b448025ee25edec02e7faa87cd2712f56c0ea862 Mon Sep 17 00:00:00 2001 From: brian Date: Mon, 18 Jan 2021 15:48:46 -0500 Subject: [PATCH] implemented flexmark & commonmark engines --- tengine/pom.xml | 32 +++ .../alfmarkdown/CommonmarkTransformer.java | 184 ++++++++++++++++++ .../alfmarkdown/FlexmarkTransformer.java | 180 +++++++++++++++++ .../alfmarkdown/RequestParamConstants.java | 9 + .../alfmarkdown/TransformerController.java | 23 ++- .../module/alfmarkdown/TransformerImpl.java | 45 ----- .../main/resources/application-default.yaml | 24 ++- .../resources/templates/transformForm.html | 4 +- .../main/resources/this_engine_config.json | 6 +- .../test/resources/application-default.yaml | 24 ++- 10 files changed, 478 insertions(+), 53 deletions(-) create mode 100755 tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/CommonmarkTransformer.java create mode 100644 tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/FlexmarkTransformer.java create mode 100755 tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/RequestParamConstants.java delete mode 100644 tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/TransformerImpl.java diff --git a/tengine/pom.xml b/tengine/pom.xml index 27a2a04..5c7c8ec 100644 --- a/tengine/pom.xml +++ b/tengine/pom.xml @@ -17,6 +17,8 @@ 2.3.6 2.3.5.RELEASE 5.15.8 + 0.17.0 + 0.62.2 docker.yateslong.us inteligr8/${project.artifactId} @@ -24,6 +26,36 @@ + + com.atlassian.commonmark + commonmark + ${commonmark.version} + + + com.atlassian.commonmark + commonmark-ext-gfm-tables + ${commonmark.version} + + + com.atlassian.commonmark + commonmark-ext-task-list-items + ${commonmark.version} + + + com.atlassian.commonmark + commonmark-ext-image-attributes + ${commonmark.version} + + + com.atlassian.commonmark + commonmark-ext-ins + ${commonmark.version} + + + com.vladsch.flexmark + flexmark-all + ${flexmark.version} + org.alfresco alfresco-transformer-base diff --git a/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/CommonmarkTransformer.java b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/CommonmarkTransformer.java new file mode 100755 index 0000000..ba7c89e --- /dev/null +++ b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/CommonmarkTransformer.java @@ -0,0 +1,184 @@ +package com.inteligr8.alfresco.module.alfmarkdown; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.PostConstruct; + +import org.alfresco.transform.exceptions.TransformException; +import org.alfresco.transformer.executors.Transformer; +import org.commonmark.Extension; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.Renderer; +import org.commonmark.renderer.html.HtmlRenderer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +@Component +public class CommonmarkTransformer implements Transformer { + + private final Logger logger = LoggerFactory.getLogger(CommonmarkTransformer.class); + private final String id = "commonmark"; + private final List classSearchPrefixes = Arrays.asList("", "org.commonmark.ext.{name}.", "org.commonmark.ext.gfm.{name}."); + private final Pattern extClassNamePattern = Pattern.compile("[\\.]?(([A-Za-z0-9]+)Extension|[A-Za-z0-9]+)$"); + + @Value("${transform.alfmarkdown.commonmark.defaultProfile}") + private String profile; + + @Value("${transform.alfmarkdown.commonmark.defaultExtensions}") + private List extensionClassNames; + + @PostConstruct + public void init() throws Exception { + if (this.logger.isDebugEnabled()) + this.logger.debug("init()"); + } + + @Override + public String getTransformerId() { + return this.id; + } + + @Override + public void extractMetadata(String transformName, String sourceMimetype, String targetMimetype, Map transformOptions, File sourceFile, File targetFile) + throws IOException { + if (this.logger.isTraceEnabled()) + this.logger.trace("extractMetadata('" + transformName + "', '" + sourceMimetype + "', '" + targetMimetype + "', " + transformOptions.keySet() + ", '" + sourceFile + "', '" + targetFile + "')"); + throw new TransformException(HttpStatus.NOT_IMPLEMENTED.value(), "This transformer does not support meta-data extraction"); + } + + @Override + public void transform(String transformName, String sourceMimetype, String targetMimetype, Map transformOptions, File sourceFile, File targetFile) + throws IOException { + if (this.logger.isTraceEnabled()) + this.logger.trace("transform('" + transformName + "', '" + sourceMimetype + "', '" + targetMimetype + "', " + transformOptions.keySet() + ", '" + sourceFile + "', '" + targetFile + "')"); + + List extensions = this.gatherExtensions(transformOptions); + + Parser parser = Parser.builder().extensions(extensions).build(); + Renderer renderer; + + switch (targetMimetype) { + case MediaType.TEXT_HTML_VALUE: + renderer = HtmlRenderer.builder().extensions(extensions).build(); + break; + default: + throw new TransformException(HttpStatus.BAD_REQUEST.value(), "This transformer does not support target data of the '" + targetMimetype + "' type"); + } + + FileWriter fwriter = new FileWriter(targetFile, false); + try { + FileReader freader = new FileReader(sourceFile); + try { + Node mddoc = parser.parseReader(freader); + renderer.render(mddoc, fwriter); + } finally { + freader.close(); + } + } finally { + fwriter.close(); + } + } + + private List gatherExtensions(Map transformOptions) { + // include default extensions + Set extClassNames = new HashSet<>(); + if (this.extensionClassNames != null) + extClassNames.addAll(this.extensionClassNames); + + // include/exclude based on passed in extensions + String extClassNamesRaw = transformOptions.get(RequestParamConstants.EXT_CLASS_NAMES); + if (extClassNamesRaw != null) { + for (String extClassName : extClassNamesRaw.split(",")) { + extClassName = extClassName.trim(); + if (extClassName.length() > 0) { + if (extClassName.startsWith("!")) { + // exclude those that start with ! + extClassNames.remove(extClassName.substring(1)); + } else { + // include the rest + extClassNames.add(extClassName); + } + } + } + } + + List exts = new ArrayList<>(extClassNames.size()); + + // create the extension classes using reflection + for (String extClassName : extClassNames) { + Class extClass = this.findClass(extClassName); + if (extClass != null) { + Extension ext = this.createExtension(extClass); + if (ext != null) + exts.add(ext); + } + } + + return exts; + } + + private Class findClass(String className) { + Matcher matcher = this.extClassNamePattern.matcher(className); + String extName = matcher.find() ? matcher.group(2) : null; + String extPackageName = this.camel2package(extName); + + for (String prefix : this.classSearchPrefixes) { + String classPrefix = prefix.replaceAll("\\{name\\}", extPackageName); + + try { + Class extClass = Class.forName(classPrefix + className); + return extClass; + } catch (ClassNotFoundException cnfe) { + // suppress + } + } + + return null; + } + + private Extension createExtension(Class extClass) { + try { + // finding public static method, so using "DeclaredMethod" instead of "Method" + Method createMethod = extClass.getDeclaredMethod("create"); + // calling public static method, so no instance (null) + return (Extension)createMethod.invoke(null); + } catch (NoSuchMethodException nsme) { + this.logger.warn("The '" + extClass.getName() + "' extension class must have a public static 'create' method"); + } catch (InvocationTargetException ite) { + this.logger.warn("The '" + extClass.getName() + "' extension class 'create' method encountered an unexpected exception: " + ite.getMessage()); + } catch (IllegalAccessException iae) { + this.logger.warn("The '" + extClass.getName() + "' extension class 'create' method is not public: " + iae.getMessage()); + } + + return null; + } + + private String camel2package(String str) { + StringJoiner joiner = new StringJoiner("."); + Pattern pattern = Pattern.compile("[A-Z][^A-Z]+"); + Matcher matcher = pattern.matcher(str); + while (matcher.find()) + joiner.add(matcher.group().toLowerCase()); + return joiner.toString(); + } + +} diff --git a/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/FlexmarkTransformer.java b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/FlexmarkTransformer.java new file mode 100644 index 0000000..2e78a9f --- /dev/null +++ b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/FlexmarkTransformer.java @@ -0,0 +1,180 @@ +package com.inteligr8.alfresco.module.alfmarkdown; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.PostConstruct; + +import org.alfresco.transform.exceptions.TransformException; +import org.alfresco.transformer.executors.Transformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.parser.ParserEmulationProfile; +import com.vladsch.flexmark.util.ast.Document; +import com.vladsch.flexmark.util.ast.IRender; +import com.vladsch.flexmark.util.data.MutableDataSet; +import com.vladsch.flexmark.util.misc.Extension; + +@Component +public class FlexmarkTransformer implements Transformer { + + private final Logger logger = LoggerFactory.getLogger(FlexmarkTransformer.class); + private final String id = "flexmark"; + private final List classSearchPrefixes = Arrays.asList("", "com.vladsch.flexmark.ext.{name}.", "com.vladsch.flexmark.ext."); + private final Pattern extClassNamePattern = Pattern.compile("[\\.]?(([A-Za-z0-9]+)Extension|[A-Za-z0-9]+)$"); + + @Value("${transform.alfmarkdown.flexmark.defaultProfile}") + private String profile; + + @Value("${transform.alfmarkdown.flexmark.defaultExtensions}") + private List extensionClassNames; + + @PostConstruct + public void init() throws Exception { + if (this.logger.isDebugEnabled()) + this.logger.debug("init()"); + } + + @Override + public String getTransformerId() { + return this.id; + } + + @Override + public void extractMetadata(String transformName, String sourceMimetype, String targetMimetype, Map transformOptions, File sourceFile, File targetFile) + throws IOException { + if (this.logger.isTraceEnabled()) + this.logger.trace("extractMetadata('" + transformName + "', '" + sourceMimetype + "', '" + targetMimetype + "', " + transformOptions.keySet() + ", '" + sourceFile + "', '" + targetFile + "')"); + throw new TransformException(HttpStatus.NOT_IMPLEMENTED.value(), "This transformer does not support meta-data extraction"); + } + + @Override + public void transform(String transformName, String sourceMimetype, String targetMimetype, Map transformOptions, File sourceFile, File targetFile) + throws IOException { + if (this.logger.isTraceEnabled()) + this.logger.trace("transform('" + transformName + "', '" + sourceMimetype + "', '" + targetMimetype + "', " + transformOptions.keySet() + ", '" + sourceFile + "', '" + targetFile + "')"); + + String profile = transformOptions.getOrDefault(RequestParamConstants.PROFILE, this.profile); + + MutableDataSet options = new MutableDataSet(); + options.setFrom(ParserEmulationProfile.valueOf(profile.toUpperCase())); + options.set(Parser.EXTENSIONS, this.gatherExtensions(transformOptions)); + + Parser parser = Parser.builder(options).build(); + IRender renderer; + + switch (targetMimetype) { + case MediaType.TEXT_HTML_VALUE: + renderer = HtmlRenderer.builder(options).build(); + break; + default: + throw new TransformException(HttpStatus.BAD_REQUEST.value(), "This transformer does not support target data of the '" + targetMimetype + "' type"); + } + + FileWriter fwriter = new FileWriter(targetFile, false); + try { + FileReader freader = new FileReader(sourceFile); + try { + Document mddoc = parser.parseReader(freader); + renderer.render(mddoc, fwriter); + } finally { + freader.close(); + } + } finally { + fwriter.close(); + } + } + + private List gatherExtensions(Map transformOptions) { + // include default extensions + Set extClassNames = new HashSet<>(); + if (this.extensionClassNames != null) + extClassNames.addAll(this.extensionClassNames); + + // include/exclude based on passed in extensions + String extClassNamesRaw = transformOptions.get(RequestParamConstants.EXT_CLASS_NAMES); + if (extClassNamesRaw != null) { + for (String extClassName : extClassNamesRaw.split("[,\\n]")) { + extClassName = extClassName.trim(); + if (extClassName.length() > 0) { + if (extClassName.startsWith("!")) { + // exclude those that start with ! + extClassNames.remove(extClassName.substring(1)); + } else { + // include the rest + extClassNames.add(extClassName); + } + } + } + } + + List exts = new ArrayList<>(extClassNames.size()); + + // create the extension classes using reflection + for (String extClassName : extClassNames) { + Class extClass = this.findClass(extClassName); + if (extClass != null) { + Extension ext = this.createExtension(extClass); + if (ext != null) + exts.add(ext); + } + } + + return exts; + } + + private Class findClass(String className) { + Matcher matcher = this.extClassNamePattern.matcher(className); + String extName = matcher.find() ? matcher.group(2).toLowerCase() : null; + + for (String prefix : this.classSearchPrefixes) { + String classPrefix = prefix.replaceAll("\\{name\\}", extName); + + try { + Class extClass = Class.forName(classPrefix + className); + return extClass; + } catch (ClassNotFoundException cnfe) { + // suppress + } + } + + return null; + } + + private Extension createExtension(Class extClass) { + try { + // finding public static method, so using "DeclaredMethod" instead of "Method" + Method createMethod = extClass.getDeclaredMethod("create"); + // calling public static method, so no instance (null) + return (Extension)createMethod.invoke(null); + } catch (NoSuchMethodException nsme) { + this.logger.warn("The '" + extClass.getName() + "' extension class must have a public static 'create' method"); + } catch (InvocationTargetException ite) { + this.logger.warn("The '" + extClass.getName() + "' extension class 'create' method encountered an unexpected exception: " + ite.getMessage()); + } catch (IllegalAccessException iae) { + this.logger.warn("The '" + extClass.getName() + "' extension class 'create' method is not public: " + iae.getMessage()); + } + + return null; + } + +} diff --git a/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/RequestParamConstants.java b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/RequestParamConstants.java new file mode 100755 index 0000000..6bfdb73 --- /dev/null +++ b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/RequestParamConstants.java @@ -0,0 +1,9 @@ +package com.inteligr8.alfresco.module.alfmarkdown; + +public interface RequestParamConstants { + + public static final String ENGINE = "engine"; + public static final String PROFILE = "profile"; + public static final String EXT_CLASS_NAMES = "extensions"; + +} diff --git a/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/TransformerController.java b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/TransformerController.java index e0b2a22..b225738 100644 --- a/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/TransformerController.java +++ b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/TransformerController.java @@ -35,12 +35,15 @@ import java.util.regex.Pattern; import javax.annotation.PostConstruct; +import org.alfresco.transform.exceptions.TransformException; import org.alfresco.transformer.AbstractTransformerController; +import org.alfresco.transformer.executors.Transformer; import org.alfresco.transformer.probes.ProbeTestTransform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; @@ -69,9 +72,10 @@ public class TransformerController extends AbstractTransformerController { private final Logger logger = LoggerFactory.getLogger(TransformerController.class); private final Pattern fileext = Pattern.compile("\\.([^\\.]+)$"); + private final MediaType defaultTarget = MediaType.TEXT_HTML; @Autowired - private TransformerImpl transformer; + private Transformer transformer; @Value("${transform.alfmarkdown.version}") private String version; @@ -127,17 +131,30 @@ public class TransformerController extends AbstractTransformerController { } if (targetMimetype == null) { Matcher matcher = this.fileext.matcher(targetFile.getAbsolutePath()); - targetMimetype = matcher.find() ? this.ext2mime(matcher.group(1)) : MediaType.TEXT_PLAIN_VALUE; + targetMimetype = matcher.find() ? this.ext2mime(matcher.group(1)) : this.defaultTarget.toString(); } - this.transformer.transform(transformName, sourceMimetype, targetMimetype, transformOptions, sourceFile, targetFile); + if (!MediaType.TEXT_MARKDOWN_VALUE.equals(sourceMimetype)) + throw new TransformException(HttpStatus.BAD_REQUEST.value(), "This transformer does not support source data of the '" + sourceMimetype + "' type"); + + try { + this.transformer.transform(transformName, sourceMimetype, targetMimetype, transformOptions, sourceFile, targetFile); + } catch (Exception e) { + this.logger.error("The transformation encountered an unexpected issue", e); + throw new TransformException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "The transformer encountered an unexpected"); + } } private String ext2mime(String ext) { switch (ext.toLowerCase()) { // add applicable extensions here + case "md": + case "markdown": return MediaType.TEXT_MARKDOWN_VALUE; + case "html": + case "htm": return MediaType.TEXT_HTML_VALUE; case "text": case "txt": return MediaType.TEXT_PLAIN_VALUE; + case "pdf": return MediaType.APPLICATION_PDF_VALUE; default: return null; } } diff --git a/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/TransformerImpl.java b/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/TransformerImpl.java deleted file mode 100644 index 745b411..0000000 --- a/tengine/src/main/java/com/inteligr8/alfresco/module/alfmarkdown/TransformerImpl.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.inteligr8.alfresco.module.alfmarkdown; - -import java.io.File; -import java.util.Map; - -import javax.annotation.PostConstruct; - -import org.alfresco.transformer.executors.Transformer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -@Component -public class TransformerImpl implements Transformer { - - private final Logger logger = LoggerFactory.getLogger(TransformerImpl.class); - private final String id = "alfmarkdown"; - - @PostConstruct - public void init() throws Exception { - if (this.logger.isDebugEnabled()) - this.logger.debug("init()"); - } - - @Override - public String getTransformerId() { - return this.id; - } - - @Override - public void extractMetadata(String transformName, String sourceMimetype, String targetMimetype, Map transformOptions, File sourceFile, File targetFile) { - if (this.logger.isTraceEnabled()) - this.logger.trace("extractMetadata('" + transformName + "', '" + sourceMimetype + "', '" + targetMimetype + "', " + transformOptions.keySet() + ", '" + sourceFile + "', '" + targetFile + "')"); - this.transform(transformName, sourceMimetype, targetMimetype, transformOptions, sourceFile, targetFile); - } - - @Override - public void transform(String transformName, String sourceMimetype, String targetMimetype, Map transformOptions, File sourceFile, File targetFile) { - if (this.logger.isTraceEnabled()) - this.logger.trace("transform('" + transformName + "', '" + sourceMimetype + "', '" + targetMimetype + "', " + transformOptions.keySet() + ", '" + sourceFile + "', '" + targetFile + "')"); - - // TODO implement your transformation logic here - } - -} diff --git a/tengine/src/main/resources/application-default.yaml b/tengine/src/main/resources/application-default.yaml index a801cc7..cb70837 100644 --- a/tengine/src/main/resources/application-default.yaml +++ b/tengine/src/main/resources/application-default.yaml @@ -7,7 +7,29 @@ transform: location: classpath:this_engine_config.json alfmarkdown: version: ${project.version} + commonmark: + defaultExtensions: + - ImageAttributesExtension + - TaskListItemsExtension + - TablesExtension + - InsExtension + flexmark: + defaultProfile: github + defaultExtensions: + - AdmonitionExtension + - DefinitionExtension + - EmojiExtension + - EnumeratedReferenceExtension + - FootnoteExtension + - InsExtension + - MediaTagsExtension + - SubscriptExtension + - SuperscriptExtension + - TablesExtension + - TaskListExtension + - TypographicExtension + - YouTubeLinkExtension logging: level: - com.inteligr8.alfresco.module: ${LOG_LEVEL:info} + com.inteligr8.alfresco.module.alfmarkdown: ${LOG_LEVEL:info} diff --git a/tengine/src/main/resources/templates/transformForm.html b/tengine/src/main/resources/templates/transformForm.html index e3eea98..59237ed 100644 --- a/tengine/src/main/resources/templates/transformForm.html +++ b/tengine/src/main/resources/templates/transformForm.html @@ -7,7 +7,9 @@ - + + +
file *
targetExtension *
engine
profile
extensions
diff --git a/tengine/src/main/resources/this_engine_config.json b/tengine/src/main/resources/this_engine_config.json index d46a4b5..f09b016 100644 --- a/tengine/src/main/resources/this_engine_config.json +++ b/tengine/src/main/resources/this_engine_config.json @@ -1,14 +1,16 @@ { "transformOptions": { "alfmarkdownOptions": [ - // {"value": {"name": "profile"}}, + {"value": {"name": "engine"}}, + {"value": {"name": "profile"}}, + {"value": {"name": "extensions"}} ] }, "transformers": [ { "transformerName": "alfmarkdown", "supportedSourceAndTargetList": [ - // {"sourceMediaType": "text/plain", "priority": 10, "targetMediaType": "text/plain" } + {"sourceMediaType": "text/markdown", "priority": 10, "targetMediaType": "text/html" } ], "transformOptions": [ "alfmarkdownOptions" diff --git a/tengine/src/test/resources/application-default.yaml b/tengine/src/test/resources/application-default.yaml index 5210440..6082e4e 100644 --- a/tengine/src/test/resources/application-default.yaml +++ b/tengine/src/test/resources/application-default.yaml @@ -7,7 +7,29 @@ transform: location: classpath:this_engine_config.json alfmarkdown: version: ${project.version} + commonmark: + defaultExtensions: + - ImageAttributesExtension + - TaskListItemsExtension + - TablesExtension + - InsExtension + flexmark: + defaultProfile: github + defaultExtensions: + - AdmonitionExtension + - DefinitionExtension + - EmojiExtension + - EnumeratedReferenceExtension + - FootnoteExtension + - InsExtension + - MediaTagsExtension + - SubscriptExtension + - SuperscriptExtension + - TablesExtension + - TaskListExtension + - TypographicExtension + - YouTubeLinkExtension logging: level: - com.inteligr8.alfresco.module: ${LOG_LEVEL:trace} + com.inteligr8.alfresco.module.alfmarkdown: ${LOG_LEVEL:trace}