Skip to content

Commit

Permalink
Collect crash reports
Browse files Browse the repository at this point in the history
  • Loading branch information
WojciechMazur committed Dec 4, 2023
1 parent 078e17c commit 98f0397
Show file tree
Hide file tree
Showing 16 changed files with 186 additions and 70 deletions.
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,6 @@ lazy val mtags3 = project
Compile / unmanagedSourceDirectories += (ThisBuild / baseDirectory).value / "mtags-shared" / "src" / "main" / "scala-3",
moduleName := "mtags3",
scalaVersion := V.scala3,
crossScalaVersions := Seq(V.scala3),
target := (ThisBuild / baseDirectory).value / "mtags" / "target" / "target3",
publish / skip := true,
scalafixConfig := Some(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class MetalsLspService(
ThreadPools.discardRejectedRunnables("MetalsLanguageServer.ec", ec)

def getVisibleName: String = folderVisibleName.getOrElse(folder.toString())
def getTelemetryLevel: TelemetryLevel = userConfig.telemetryLevel

private val cancelables = new MutableCancelable()
val isCancelled = new AtomicBoolean(false)
Expand Down
26 changes: 26 additions & 0 deletions metals/src/main/scala/scala/meta/metals/Main.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scala.meta.metals

import java.util.concurrent.Executors
import java.util.Optional

import scala.concurrent.ExecutionContext
import scala.util.control.NonFatal
Expand All @@ -10,6 +11,9 @@ import scala.meta.internal.metals.ScalaVersions
import scala.meta.internal.metals.Trace
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.internal.metals.logging.MetalsLogger
import scala.meta.internal.metals.TelemetryClient
import scala.meta.internal.telemetry.CrashReport
import scala.meta.internal.telemetry.ExceptionSummary

import org.eclipse.lsp4j.jsonrpc.Launcher

Expand Down Expand Up @@ -63,6 +67,7 @@ object Main {
launcher.startListening().get()
} catch {
case NonFatal(e) =>
trySendCrashReport(e, server)
e.printStackTrace(systemOut)
sys.exit(1)
} finally {
Expand All @@ -74,4 +79,25 @@ object Main {
}
}

private def trySendCrashReport(
error: Throwable,
server: MetalsLanguageServer,
): Unit = try {
val telemetryLevel = server.getTelemetryLevel()
if (telemetryLevel.reportCrash) {
val telemetry = new TelemetryClient(() => telemetryLevel)
telemetry.sendCrashReport(
new CrashReport(
ExceptionSummary.fromThrowable(error, identity),
this.getClass().getName(),
Optional.of(BuildInfo.metalsVersion),
Optional.empty(),
)
)
}
} catch {
case err: Throwable =>
System.err.println(s"Failed to send crash report, $err")
}

}
17 changes: 17 additions & 0 deletions metals/src/main/scala/scala/meta/metals/MetalsLanguageServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import scala.meta.internal.metals.MutableCancelable
import scala.meta.internal.metals.StdReportContext
import scala.meta.internal.metals.ThreadPools
import scala.meta.internal.metals.WorkspaceLspService
import scala.meta.internal.metals.TelemetryLevel
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.internal.metals.clients.language.NoopLanguageClient
import scala.meta.internal.metals.logging.MetalsLogger
Expand Down Expand Up @@ -283,4 +284,20 @@ class MetalsLanguageServer(
case _ => throw new IllegalStateException("Server is not initialized")
}

private[metals] def getTelemetryLevel() = {
def maxConfiguredTelemetryLevel(service: WorkspaceLspService) = {
val entries =
service.workspaceFolders.getFolderServices.map(_.getTelemetryLevel)
if (entries.isEmpty) TelemetryLevel.default
else entries.max
}
serverState.get() match {
case ServerState.Initialized(service) =>
maxConfiguredTelemetryLevel(service)
case ServerState.ShuttingDown(service) =>
maxConfiguredTelemetryLevel(service)
case _ => TelemetryLevel.default
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.meta.internal.telemetry

import scala.util.Random
import scala.util.Try
import scala.util.control.NonFatal

import java.io.InputStreamReader

Expand Down Expand Up @@ -79,7 +80,7 @@ object TelemetryClient {
}
}

private class TelemetryClient(
private[meta] class TelemetryClient(
telemetryLevel: () => TelemetryLevel,
config: TelemetryClient.Config = TelemetryClient.Config.default,
logger: LoggerAccess = LoggerAccess.system
Expand All @@ -89,12 +90,26 @@ private class TelemetryClient(

implicit private def clientConfig: Config = config

private val SendReportEvent = new Endpoint(api.SendReportEventEndpoint)
private val SendErrorReport = new Endpoint(api.SendErrorReportEndpoint)
private val SendCrashReport = new Endpoint(api.SendCrashReportEndpoint)

override def sendReportEvent(event: telemetry.ReportEvent): Unit =
override def sendErrorReport(report: telemetry.ErrorReport): Unit =
if (telemetryLevel().reportErrors) {
SendReportEvent(event).recover { case err =>
logger.warning(s"Failed to send report: ${err}")
}
SendErrorReport(report)
.recover { case NonFatal(err) =>
logSendFailure(reportType = "error")(err)
}
}

override def sendCrashReport(report: telemetry.CrashReport): Unit =
if (telemetryLevel().reportCrash) {
SendCrashReport(report)
.recover { case NonFatal(err) =>
logSendFailure(reportType = "crash")(err)
}
}

private def logSendFailure(reportType: String)(error: Throwable) =
logger.debug(s"Failed to send $reportType report: ${error}")

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ sealed class TelemetryLevel(
}

object TelemetryLevel {
implicit lazy val ordering: Ordering[TelemetryLevel] = Ordering.by(_.level)

case object Off extends TelemetryLevel(0, "off")
case object Crash extends TelemetryLevel(1, "crash")
case object Error extends TelemetryLevel(2, "error")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,32 +78,17 @@ private class TelemetryReporter(
Nil
override def deleteAll(): Unit = ()

private lazy val environmentInfo: telemetry.Environment = {
def propertyOrUnknown(key: String) = sys.props.getOrElse(key, "unknown")
new telemetry.Environment(
/* java = */ new telemetry.JavaInfo(
/* version = */ propertyOrUnknown("java.version"),
/* distribution = */ sys.props.get("java.vendor").toJava
),
/* system = */ new telemetry.SystemInfo(
/* architecture = */ propertyOrUnknown("os.arch"),
/* name = */ propertyOrUnknown("os.name"),
/* version = */ propertyOrUnknown("os.version")
)
)
}

override def sanitize(message: String): String =
sanitizers.all.foldRight(message)(_.apply(_))

private def createSanitizedReport(report: Report) = new telemetry.ReportEvent(
private def createSanitizedReport(report: Report) = new telemetry.ErrorReport(
/* name = */ report.name,
/* text = */ if (sanitizers.canSanitizeSources)
Optional.of(sanitize(report.text))
else Optional.empty(),
/* id = */ report.id.toJava,
/* error = */ report.error
.map(telemetry.ReportedError.fromThrowable(_, sanitize(_)))
.map(telemetry.ExceptionSummary.fromThrowable(_, sanitize(_)))
.toJava,
/* reporterName = */ name,
/* reporterContext = */ reporterContext() match {
Expand All @@ -113,18 +98,17 @@ private class TelemetryReporter(
telemetry.ReporterContextUnion.scalaPresentationCompiler(ctx)
case ctx: telemetry.UnknownProducerContext =>
telemetry.ReporterContextUnion.unknown(ctx)
},
/* env = */ environmentInfo
}
)

override def create(
unsanitizedReport: => Report,
ifVerbose: Boolean
): Option[Path] = {
if (telemetryLevel().reportErrors) {
val event = createSanitizedReport(unsanitizedReport)
if (event.getText().isPresent() || event.getError().isPresent())
client.sendReportEvent(event)
val report = createSanitizedReport(unsanitizedReport)
if (report.getText().isPresent() || report.getError().isPresent())
client.sendErrorReport(report)
else
logger.info(
"Skiped reporting remotely unmeaningful report, no context or error, reportId=" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import org.eclipse.lsp4j.SelectionRange
import org.eclipse.lsp4j.SignatureHelp
import org.eclipse.lsp4j.TextEdit


case class ScalaPresentationCompiler(
buildTargetIdentifier: String = "",
buildTargetName: Option[String] = None,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package scala.meta.internal.telemetry;

import java.util.Optional;

public class CrashReport {
final private ExceptionSummary error;
final private String componentName;
final private Environment env;
final private Optional<String> componentVersion;
final private Optional<ReporterContextUnion> reporterContext;

public CrashReport(ExceptionSummary error, String componentName, Optional<String> componentVersion,
Optional<ReporterContextUnion> reporterContext) {
this(error, componentName, Environment.get(), componentVersion, reporterContext);
}

public CrashReport(ExceptionSummary error, String componentName, Environment env, Optional<String> componentVersion,
Optional<ReporterContextUnion> reporterContext) {
this.error = error;
this.componentName = componentName;
this.env = env;
this.componentVersion = componentVersion;
this.reporterContext = reporterContext;
}

public ExceptionSummary getError() {
return error;
}

public String getComponentName() {
return componentName;
}

public Optional<String> getComponentVersion() {
return componentVersion;
}

public Optional<ReporterContextUnion> getReporterContext() {
return reporterContext;
}

public Environment getEnv() {
return env;
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
package scala.meta.internal.telemetry;

public class Environment {
private JavaInfo java;
private SystemInfo system;
final private JavaInfo java;
final private SystemInfo system;

public Environment(JavaInfo java, SystemInfo system) {
this.java = java;
this.system = system;
private final static Environment instance;

public static Environment get() {
return instance;
}

public void setJava(JavaInfo java) {
this.java = java;
static {
instance = new Environment(
new JavaInfo(System.getProperty("java.version", "unknown"),
System.getProperty("java.vendor", "unknown")),
new SystemInfo(System.getProperty("os.arch", "unknown"), System.getProperty("os.name", "unknown"),
System.getProperty("os.version", "unknown")));
}

public void setSystem(SystemInfo system) {
// Generated

public Environment(JavaInfo java, SystemInfo system) {
this.java = java;
this.system = system;
}

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

import java.util.Optional;

public class ReportEvent {
public class ErrorReport {
final private String name;
final private Optional<String> text;
final private Optional<String> id;
final private Optional<ReportedError> error;
final private Optional<ExceptionSummary> error;
final private String reporterName;
final private ReporterContextUnion reporterContext;
final private Environment env;

public ReportEvent(String name, Optional<String> text, Optional<String> id, Optional<ReportedError> error,
public ErrorReport(String name, Optional<String> text, Optional<String> id, Optional<ExceptionSummary> error,
String reporterName, ReporterContextUnion reporterContext) {
this(name, text, id, error, reporterName, reporterContext, Environment.get());
}

public ErrorReport(String name, Optional<String> text, Optional<String> id, Optional<ExceptionSummary> error,
String reporterName, ReporterContextUnion reporterContext, Environment env) {
this.name = name;
this.text = text;
Expand All @@ -34,7 +39,7 @@ public Optional<String> getId() {
return id;
}

public Optional<ReportedError> getError() {
public Optional<ExceptionSummary> getError() {
return error;
}

Expand Down Expand Up @@ -72,7 +77,7 @@ public boolean equals(Object obj) {
return false;
if (getClass() != obj.getClass())
return false;
ReportEvent other = (ReportEvent) obj;
ErrorReport other = (ErrorReport) obj;
if (name == null) {
if (other.name != null)
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import java.util.List;
import java.util.function.Function;

public class ReportedError {
public class ExceptionSummary {
final private List<String> exceptions;
final private String stacktrace;

public ReportedError(List<String> exceptions, String stacktrace) {
public ExceptionSummary(List<String> exceptions, String stacktrace) {
this.exceptions = exceptions;
this.stacktrace = stacktrace;
}
Expand All @@ -23,7 +23,7 @@ public String getStacktrace() {
}


public static ReportedError fromThrowable(Throwable exception, Function<String, String> sanitizer) {
public static ExceptionSummary fromThrowable(Throwable exception, Function<String, String> sanitizer) {
List<String> exceptions = new java.util.LinkedList<>();
for (Throwable current = exception; current != null; current = current.getCause()) {
exceptions.add(current.getClass().getName());
Expand All @@ -33,7 +33,7 @@ public static ReportedError fromThrowable(Throwable exception, Function<String,
exception.printStackTrace(pw);
}
String stacktrace = sanitizer.apply(stringWriter.toString());
return new ReportedError(exceptions, stacktrace);
return new ExceptionSummary(exceptions, stacktrace);
}

@Override
Expand All @@ -53,7 +53,7 @@ public boolean equals(Object obj) {
return false;
if (getClass() != obj.getClass())
return false;
ReportedError other = (ReportedError) obj;
ExceptionSummary other = (ExceptionSummary) obj;
if (exceptions == null) {
if (other.exceptions != null)
return false;
Expand Down

0 comments on commit 98f0397

Please sign in to comment.