Skip to content

Commit

Permalink
Implement the Witchcraft Logging API Idea plugin (#309)
Browse files Browse the repository at this point in the history
* Implement the Witchcraft Logging API Idea plugin

* Add generated changelog entries

* defensive check

* depends on the platform

Co-authored-by: svc-changelog <[email protected]>
  • Loading branch information
carterkozak and svc-changelog authored Aug 5, 2021
1 parent 3380d2b commit 644c5d3
Show file tree
Hide file tree
Showing 39 changed files with 2,295 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ buildscript {
classpath 'com.palantir.gradle.gitversion:gradle-git-version:0.12.3'
classpath 'com.palantir.javaformat:gradle-palantir-java-format:2.2.0'
classpath 'gradle.plugin.org.inferred:gradle-processors:3.3.0'
classpath 'org.jetbrains.intellij.plugins:gradle-intellij-plugin:1.1.4'
}
}

Expand Down
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-309.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: feature
feature:
description: Implement the Witchcraft Logging API Idea plugin
links:
- https://github.com/palantir/witchcraft-api/pull/309
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ include 'witchcraft-health-api'
include 'witchcraft-health-api:witchcraft-health-api-objects'
include 'witchcraft-logging-api'
include 'witchcraft-logging-api:witchcraft-logging-api-objects'
include 'witchcraft-logging-idea'
4 changes: 4 additions & 0 deletions versions.props
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
com.palantir.conjure.java:* = 6.1.0
com.palantir.conjure:conjure = 4.16.1
org.immutables:* = 2.8.8
org.assertj:assertj-core = 3.20.2
org.junit.jupiter:* = 5.7.2
org.mockito:* = 3.11.2
62 changes: 62 additions & 0 deletions witchcraft-logging-idea/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
apply plugin: 'java-library'
apply plugin: 'maven-publish'
apply plugin: 'org.jetbrains.intellij'
apply plugin: 'org.inferred.processors'
apply plugin: 'com.palantir.external-publish-jar'

sourceCompatibility = 11

dependencies {
implementation project(':witchcraft-logging-api:witchcraft-logging-api-objects'), {
exclude group: 'org.slf4j'
}
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8'

annotationProcessor 'org.immutables:value'
compileOnly 'org.immutables:value-annotations'

testImplementation 'org.assertj:assertj-core'
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.mockito:mockito-core'
}

versionRecommendations {
excludeConfigurations 'idea'
}

versionsLock {
// 'org.jetbrains.intellij' creates a dependency on *IntelliJ*, which GCV cannot resolve
disableJavaPluginDefaults()
}

def minimumIntellijBuild = '211.7628.21' // 2021.1.3
intellij {
pluginName = 'witchcraft-logging-idea'
version = minimumIntellijBuild
updateSinceUntilBuild = false
}

patchPluginXml {
pluginDescription = "Renders Witchcraft logging api logs from structured data into human-readable text."
version = project.version
sinceBuild = minimumIntellijBuild
}

publishPlugin {
token = System.env.JETBRAINS_PLUGIN_REPO_TOKEN
}

check.dependsOn buildPlugin, verifyPlugin
tasks.publishPlugin.onlyIf { versionDetails().isCleanTag && System.env.JETBRAINS_PLUGIN_REPO_TOKEN != null }
tasks.publish.dependsOn publishPlugin
buildSearchableOptions.enabled = false
// Prevent nebula.maven-publish from trying to publish components.java - we are publishing our own different artifact
ext."nebulaPublish.maven.jar" = false
publishing {
publications {
nebula(MavenPublication) {
artifact buildPlugin
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.witchcraft.logging.format;

import com.palantir.witchcraft.api.logging.AuditLogV2;
import com.palantir.witchcraft.api.logging.DiagnosticLogV1;
import com.palantir.witchcraft.api.logging.EventLogV2;
import com.palantir.witchcraft.api.logging.MetricLogV1;
import com.palantir.witchcraft.api.logging.RequestLogV2;
import com.palantir.witchcraft.api.logging.ServiceLogV1;
import com.palantir.witchcraft.api.logging.TraceLogV1;
import java.util.Optional;
import java.util.function.BiFunction;

final class CombineWithLogVisitor<T, U, R> implements LogVisitor<R> {
private final LogVisitor<T> first;
private final LogVisitor<U> second;
private final BiFunction<T, U, R> combiner;

CombineWithLogVisitor(LogVisitor<T> first, LogVisitor<U> second, BiFunction<T, U, R> combiner) {
this.first = first;
this.second = second;
this.combiner = combiner;
}

@Override
public Optional<R> serviceV1(ServiceLogV1 serviceLogV1) {
return combineWith(serviceLogV1, LogVisitor::serviceV1);
}

@Override
public Optional<R> requestV2(RequestLogV2 requestLogV2) {
return combineWith(requestLogV2, LogVisitor::requestV2);
}

@Override
public Optional<R> eventV2(EventLogV2 eventLogV2) {
return combineWith(eventLogV2, LogVisitor::eventV2);
}

@Override
public Optional<R> metricV1(MetricLogV1 metricLogV1) {
return combineWith(metricLogV1, LogVisitor::metricV1);
}

@Override
public Optional<R> traceV1(TraceLogV1 traceLogV1) {
return combineWith(traceLogV1, LogVisitor::traceV1);
}

@Override
public Optional<R> auditV2(AuditLogV2 auditLogV2) {
return combineWith(auditLogV2, LogVisitor::auditV2);
}

@Override
public Optional<R> diagnosticV1(DiagnosticLogV1 diagnosticLogV1) {
return combineWith(diagnosticLogV1, LogVisitor::diagnosticV1);
}

private <L> Optional<R> combineWith(L log, LogFunctionSelector<L> logFunc) {
return logFunc.apply(first, log).flatMap(t -> {
return logFunc.apply(second, log).map(u -> combiner.apply(t, u));
});
}

private interface LogFunctionSelector<L> {
<X> Optional<X> apply(LogVisitor<X> logVisitor, L logLine);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.witchcraft.logging.format;

import com.palantir.witchcraft.api.logging.EventLogV2;
import java.time.format.DateTimeFormatter;

final class EventLogFormatter {
private EventLogFormatter() {}

static String format(EventLogV2 event) {
StringBuilder buffer = new StringBuilder().append('[');
DateTimeFormatter.ISO_INSTANT.formatTo(event.getTime(), buffer);
buffer.append("] ").append(event.getEventName());
if (!event.getValues().isEmpty()) {
buffer.append(' ');
Formatting.niceMap(event.getValues(), buffer);
}
if (!event.getUnsafeParams().isEmpty()) {
buffer.append(' ');
Formatting.niceMap(event.getUnsafeParams(), buffer);
}
return buffer.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.witchcraft.logging.format;

import com.google.common.base.CharMatcher;
import java.util.Map;
import java.util.function.Consumer;

/** Utility functionality shared between {@link LogFormatter} implementations. */
final class Formatting {

static final CharMatcher NEWLINE_MATCHER = CharMatcher.is('\n');

private static final ThreadLocal<StringBuilder> REUSABLE_STRING_BUILDER =
ThreadLocal.withInitial(() -> new StringBuilder(1024));

static void niceMap(Map<String, ?> params, StringBuilder sb) {
sb.append('(');
formatParamsTo(params, sb);
if (!params.isEmpty()) {
sb.setLength(sb.length() - 2);
}
sb.append(')');
}

static void formatParamsTo(Map<String, ?> params, StringBuilder sb) {
for (Map.Entry<String, ?> entry : params.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
sb.append(key).append(": ").append(safeString(value)).append(", ");
}
}

static String safeString(Object value) {
try {
return String.valueOf(value);
} catch (RuntimeException e) {
// Fallback if toString throws
return value.getClass().getSimpleName() + '@' + System.identityHashCode(value);
}
}

static String withStringBuilder(Consumer<StringBuilder> function) {
StringBuilder builder = REUSABLE_STRING_BUILDER.get();
builder.setLength(0);
function.accept(builder);
String result = builder.toString();
if (builder.length() > 1024 * 16) {
// Buffer has grown too large, allow the instance to be collected
REUSABLE_STRING_BUILDER.remove();
} else {
builder.setLength(0);
}
return result;
}

private Formatting() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.witchcraft.logging.format;

import com.palantir.witchcraft.api.logging.EventLogV2;
import com.palantir.witchcraft.api.logging.MetricLogV1;
import com.palantir.witchcraft.api.logging.RequestLogV2;
import com.palantir.witchcraft.api.logging.ServiceLogV1;
import com.palantir.witchcraft.api.logging.TraceLogV1;
import java.util.Optional;

public enum LogFormatter implements LogVisitor<String> {
INSTANCE;

@Override
public Optional<String> serviceV1(ServiceLogV1 serviceLogV1) {
return Optional.of(ServiceLogFormatter.format(serviceLogV1));
}

@Override
public Optional<String> requestV2(RequestLogV2 requestLogV2) {
return Optional.of(RequestLogFormatter.format(requestLogV2));
}

@Override
public Optional<String> eventV2(EventLogV2 eventLogV2) {
return Optional.of(EventLogFormatter.format(eventLogV2));
}

@Override
public Optional<String> metricV1(MetricLogV1 metricLogV1) {
return Optional.of(MetricLogFormatter.format(metricLogV1));
}

@Override
public Optional<String> traceV1(TraceLogV1 traceLogV1) {
return Optional.of(TraceLogFormatter.format(traceLogV1));
}
}
Loading

0 comments on commit 644c5d3

Please sign in to comment.