Skip to content

Commit

Permalink
Merge branch 'master' into nikita-tkachenko/fail-fast-on-tracer-versi…
Browse files Browse the repository at this point in the history
…on-mismatch
  • Loading branch information
nikita-tkachenko-datadog authored Nov 9, 2023
2 parents 7893b87 + 2e6162b commit 2731f44
Show file tree
Hide file tree
Showing 41 changed files with 964 additions and 400 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@
import com.datadog.debugger.probe.LogProbe;
import datadog.trace.bootstrap.debugger.CapturedContext;
import datadog.trace.bootstrap.debugger.EvaluationError;
import datadog.trace.bootstrap.debugger.Limits;
import datadog.trace.bootstrap.debugger.util.Redaction;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogMessageTemplateBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(LogMessageTemplateBuilder.class);
public class StringTemplateBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(StringTemplateBuilder.class);
/**
* Serialization limits for log messages. Most values are lower than snapshot because you can
* directly reference values that are in your interest with Expression Language:
* obj.field.deepfield or array[1001]
*/
private final List<LogProbe.Segment> segments;

public LogMessageTemplateBuilder(List<LogProbe.Segment> segments) {
private final Limits limits;

public StringTemplateBuilder(List<LogProbe.Segment> segments, Limits limits) {
this.segments = segments;
this.limits = limits;
}

public String evaluate(CapturedContext context, LogProbe.LogStatus status) {
Expand All @@ -45,7 +49,8 @@ public String evaluate(CapturedContext context, LogProbe.LogStatus status) {
} else if (result.isNull()) {
sb.append("null");
} else {
serializeValue(sb, segment.getParsedExpr().getDsl(), result.getValue(), status);
serializeValue(
sb, segment.getParsedExpr().getDsl(), result.getValue(), status, limits);
}
} catch (EvaluationException ex) {
LOGGER.debug("Evaluation error: ", ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import com.datadog.debugger.agent.DebuggerAgent;
import com.datadog.debugger.agent.Generated;
import com.datadog.debugger.agent.LogMessageTemplateBuilder;
import com.datadog.debugger.agent.StringTemplateBuilder;
import com.datadog.debugger.el.EvaluationException;
import com.datadog.debugger.el.ProbeCondition;
import com.datadog.debugger.el.ValueScript;
Expand Down Expand Up @@ -41,6 +41,8 @@
/** Stores definition of a log probe */
public class LogProbe extends ProbeDefinition {
private static final Logger LOGGER = LoggerFactory.getLogger(LogProbe.class);
private static final Limits LIMITS = new Limits(1, 3, 8192, 5);
private static final int LOG_MSG_LIMIT = 8192;

/** Stores part of a templated message either a str or an expression */
public static class Segment {
Expand Down Expand Up @@ -386,8 +388,15 @@ public void evaluate(
sample(logStatus, methodLocation);
}
if (logStatus.isSampled() && logStatus.getCondition()) {
LogMessageTemplateBuilder logMessageBuilder = new LogMessageTemplateBuilder(segments);
logStatus.setMessage(logMessageBuilder.evaluate(context, logStatus));
StringTemplateBuilder logMessageBuilder = new StringTemplateBuilder(segments, LIMITS);
String msg = logMessageBuilder.evaluate(context, logStatus);
if (msg != null && msg.length() > LOG_MSG_LIMIT) {
StringBuilder sb = new StringBuilder(LOG_MSG_LIMIT + 3);
sb.append(msg, 0, LOG_MSG_LIMIT);
sb.append("...");
msg = sb.toString();
}
logStatus.setMessage(msg);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.datadog.debugger.agent.DebuggerAgent;
import com.datadog.debugger.agent.Generated;
import com.datadog.debugger.agent.LogMessageTemplateBuilder;
import com.datadog.debugger.agent.StringTemplateBuilder;
import com.datadog.debugger.el.EvaluationException;
import com.datadog.debugger.el.ProbeCondition;
import com.datadog.debugger.instrumentation.CapturedContextInstrumentor;
Expand Down Expand Up @@ -33,6 +33,7 @@ public class SpanDecorationProbe extends ProbeDefinition {
private static final Logger LOGGER = LoggerFactory.getLogger(SpanDecorationProbe.class);
private static final String PROBEID_DD_TAGS_FORMAT = "_dd.di.%s.probe_id";
private static final String EVALERROR_DD_TAGS_FORMAT = "_dd.di.%s.evaluation_error";
private static final Limits LIMITS = new Limits(1, 3, 255, 5);

public enum TargetSpan {
ACTIVE,
Expand Down Expand Up @@ -166,7 +167,7 @@ public void evaluate(
SpanDecorationStatus spanStatus = (SpanDecorationStatus) status;
for (Tag tag : decoration.tags) {
String tagName = sanitize(tag.name);
LogMessageTemplateBuilder builder = new LogMessageTemplateBuilder(tag.value.getSegments());
StringTemplateBuilder builder = new StringTemplateBuilder(tag.value.getSegments(), LIMITS);
LogProbe.LogStatus logStatus = new LogProbe.LogStatus(this);
String tagValue = builder.evaluate(context, logStatus);
if (logStatus.hasLogTemplateErrors()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.datadog.debugger.agent.DebuggerAgent;
import com.datadog.debugger.util.ExceptionHelper;
import com.datadog.debugger.util.SnapshotSlicer;
import com.datadog.debugger.util.SnapshotPruner;
import datadog.trace.api.Config;
import datadog.trace.relocate.api.RatelimitedLogger;
import datadog.trace.util.TagsHelper;
Expand Down Expand Up @@ -64,20 +64,13 @@ public boolean offer(Snapshot snapshot) {

String serializeSnapshot(String serviceName, Snapshot snapshot) {
String str = DebuggerAgent.getSnapshotSerializer().serializeSnapshot(serviceName, snapshot);
int currentMaxDepth = snapshot.getMaxDepth();
while (str.length() > MAX_SNAPSHOT_SIZE && currentMaxDepth >= 0) {
String prunedStr = SnapshotPruner.prune(str, MAX_SNAPSHOT_SIZE, 4);
if (prunedStr.length() != str.length()) {
LOGGER.debug(
"serializing snapshot breached 1MB limit: {}, reducing depth level {} -> {}",
"serializing snapshot breached 1MB limit, reducing size from {} -> {}",
str.length(),
currentMaxDepth,
currentMaxDepth - 1);
currentMaxDepth -= 1;
str = SnapshotSlicer.slice(currentMaxDepth, str);
prunedStr.length());
}
if (str.length() > MAX_SNAPSHOT_SIZE) {
ratelimitedLogger.warn(
"Snapshot is too large even after reducing depth to 0: {}", str.length());
}
return str;
return prunedStr;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
package com.datadog.debugger.util;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.function.Supplier;

public class SnapshotPruner {
private static final String NOT_CAPTURED_REASON = "notCapturedReason";
private static final String DEPTH = "depth";
private static final String PRUNED = "{\"pruned\":true}";

private State state = State.OBJECT;
private final Deque<Node> stack = new ArrayDeque<>(32);
private int currentLevel;
private int strMatchIdx;
private Supplier<State> onStringMatches;
private String matchingString;
private Node root;

public static String prune(String snapshot, int maxTargetedSize, int minLevel) {
int delta = snapshot.length() - maxTargetedSize;
if (delta <= 0) {
return snapshot;
}
SnapshotPruner snapshotPruner = new SnapshotPruner(snapshot);
Collection<Node> leaves = snapshotPruner.getLeaves(minLevel);
PriorityQueue<Node> sortedLeaves =
new PriorityQueue<>(
Comparator.comparing((Node n) -> n.notCapturedDepth)
.thenComparingInt((Node n) -> n.level)
.thenComparing((Node n) -> n.notCaptured)
.thenComparingInt(Node::size)
.reversed());
sortedLeaves.addAll(leaves);
int total = 0;
Map<Integer, Node> nodes = new HashMap<>();
while (!sortedLeaves.isEmpty()) {
Node leaf = sortedLeaves.poll();
nodes.put(leaf.start, leaf);
total += leaf.size() - PRUNED.length();
if (total > delta) break;
Node parent = leaf.parent;
if (parent == null) {
break;
}
parent.pruned++;
if (parent.pruned >= parent.children.size() && parent.level >= minLevel) {
// We have pruned all the children of this parent node, so we can
// treat it as a leaf now.
parent.notCaptured = true;
parent.notCapturedDepth = true;
sortedLeaves.offer(parent);
for (Node child : parent.children) {
nodes.remove(child.start);
total -= child.size() - PRUNED.length();
}
}
}
List<Node> prunedNodes = new ArrayList<>(nodes.values());
prunedNodes.sort(Comparator.comparing((Node n) -> n.start));
StringBuilder sb = new StringBuilder();
sb.append(snapshot, 0, prunedNodes.get(0).start);
for (int i = 1; i < prunedNodes.size(); i++) {
sb.append(PRUNED);
sb.append(snapshot, prunedNodes.get(i - 1).end + 1, prunedNodes.get(i).start);
}
sb.append(PRUNED);
sb.append(snapshot, prunedNodes.get(prunedNodes.size() - 1).end + 1, snapshot.length());
return sb.toString();
}

private Collection<Node> getLeaves(int minLevel) {
if (root == null) {
return Collections.emptyList();
}
return root.getLeaves(minLevel);
}

private SnapshotPruner(String snapshot) {
for (int i = 0; i < snapshot.length(); i++) {
state = state.parse(this, snapshot.charAt(i), i);
if (state == null) {
break;
}
}
}

enum State {
OBJECT {
@Override
public State parse(SnapshotPruner pruner, char c, int index) {
switch (c) {
case '{':
{
Node n = new Node(index, pruner.currentLevel);
pruner.currentLevel++;
if (!pruner.stack.isEmpty()) {
n.parent = pruner.stack.peekLast();
n.parent.children.add(n);
}
pruner.stack.addLast(n);
return this;
}
case '}':
{
Node n = pruner.stack.removeLast();
n.end = index;
pruner.currentLevel--;
if (pruner.stack.isEmpty()) {
pruner.root = n;
return null;
}
return this;
}
case '"':
{
pruner.strMatchIdx = 0;
pruner.matchingString = NOT_CAPTURED_REASON;
pruner.onStringMatches =
() -> {
Node n = pruner.stack.peekLast();
if (n == null) {
throw new IllegalStateException("empty stack");
}
n.notCaptured = true;
return NOT_CAPTURED;
};
return STRING;
}
default:
return this;
}
}
},
STRING {
@Override
public State parse(SnapshotPruner pruner, char c, int index) {
switch (c) {
case '"':
{
if (pruner.strMatchIdx == pruner.matchingString.length()) {
return pruner.onStringMatches.get();
}
return OBJECT;
}
case '\\':
{
pruner.strMatchIdx = -1;
return ESCAPE;
}
default:
if (pruner.strMatchIdx > -1) {
char current = pruner.matchingString.charAt(pruner.strMatchIdx++);
if (c != current) {
pruner.strMatchIdx = -1;
}
}
return this;
}
}
},
NOT_CAPTURED {
@Override
public State parse(SnapshotPruner pruner, char c, int index) {
switch (c) {
case '"':
{
pruner.strMatchIdx = 0;
pruner.matchingString = DEPTH;
pruner.onStringMatches =
() -> {
Node n = pruner.stack.peekLast();
if (n == null) {
throw new IllegalStateException("empty stack");
}
n.notCapturedDepth = true;
return OBJECT;
};
return STRING;
}
case ' ':
case ':':
case '\n':
case '\t':
case '\r':
return this;
default:
return OBJECT;
}
}
},
ESCAPE {
@Override
public State parse(SnapshotPruner pruner, char c, int index) {
return STRING;
}
};

public abstract State parse(SnapshotPruner pruner, char c, int index);
}

private static class Node {
int pruned;
Node parent;
List<Node> children = new ArrayList<>();
final int start;
int end;
final int level;
boolean notCaptured;
boolean notCapturedDepth;

public Node(int start, int level) {
this.start = start;
this.level = level;
}

public Collection<Node> getLeaves(int minLevel) {
if (children.isEmpty() && level >= minLevel) {
return Collections.singleton(this);
}
Collection<Node> results = new ArrayList<>();
for (int i = children.size() - 1; i >= 0; i--) {
results.addAll(children.get(i).getLeaves(minLevel));
}
return results;
}

public int size() {
return end - start + 1;
}
}
}
Loading

0 comments on commit 2731f44

Please sign in to comment.