1+ // Copyright (c) Microsoft Corporation. All rights reserved.
2+ // Licensed under the MIT license.
3+ import * as vscode from "vscode" ;
4+ import { sendInfo } from "vscode-extension-telemetry-wrapper" ;
5+ import { LSDaemon } from "../daemon" ;
6+
7+ export class ClientLogWatcher {
8+ private context : vscode . ExtensionContext ;
9+ private javaExtensionRoot : vscode . Uri | undefined ;
10+ private logProcessedTimestamp : number = Date . now ( ) ;
11+
12+ constructor ( daemon : LSDaemon ) {
13+ this . context = daemon . context ;
14+ if ( this . context . storageUri ) {
15+ this . javaExtensionRoot = vscode . Uri . joinPath ( this . context . storageUri , ".." , "redhat.java" ) ;
16+ }
17+ }
18+
19+ public async collectStartupInfo ( ) {
20+ const logs = await this . getLogs ( ) ;
21+ if ( logs ) {
22+ const info : any = { } ;
23+
24+ const jdkLog = logs . find ( log => log . message . startsWith ( "Use the JDK from" ) ) ;
25+ info . defaultProjectJdk = jdkLog ?. message . replace ( "Use the JDK from '" , "" ) . replace ( "' as the initial default project JDK." , "" ) ;
26+
27+ const startupLog = logs . find ( log => log . message . startsWith ( "Starting Java server with:" ) && log . message . endsWith ( "jdt_ws" ) /* limit to standard server */ ) ;
28+ if ( startupLog ) {
29+ info . xmx = startupLog . message . match ( / - X m x [ 0 - 9 k m g K M G ] + / g) ?. [ 0 ] ;
30+ info . xms = startupLog . message . match ( / - X m s [ 0 - 9 k m g K M G ] + / g) ?. [ 0 ] ;
31+ info . lombok = startupLog . message . includes ( "lombok.jar" ) ? "true" : undefined ;
32+ info . workspaceType = startupLog . message . match ( / - X X : H e a p D u m p P a t h = .* ( v s c o d e s w s ) / ) ? "vscodesws" : "folder" ;
33+ }
34+
35+ const errorLog = logs . find ( log => log . level === "error" ) ;
36+ info . error = errorLog ? "true" : undefined ;
37+
38+ const missingJar = "Error opening zip file or JAR manifest missing" ; // lombok especially
39+ if ( logs . find ( log => log . message . startsWith ( missingJar ) ) ) {
40+ info . error = missingJar ;
41+ }
42+
43+ const crashLog = logs . find ( log => log . message . startsWith ( "The Language Support for Java server crashed and will restart." ) ) ;
44+ info . crash = crashLog ? "true" : undefined ;
45+
46+ sendInfo ( "" , {
47+ name : "client-log-startup-metadata" ,
48+ ...info
49+ } ) ;
50+ }
51+ }
52+
53+ public async getLogs ( ) {
54+ const rawBytes = await this . readLatestLogFile ( ) ;
55+ if ( rawBytes ) {
56+ const content = rawBytes . toString ( ) ;
57+ const entries = parse ( content ) ;
58+ return entries . filter ( elem => Date . parse ( elem [ "timestamp" ] ) > this . logProcessedTimestamp ) ;
59+ } else {
60+ return undefined ;
61+ }
62+ }
63+
64+ private async readLatestLogFile ( ) {
65+ if ( this . javaExtensionRoot ) {
66+ const files = await vscode . workspace . fs . readDirectory ( this . javaExtensionRoot ) ;
67+ const logFiles = files . filter ( elem => elem [ 0 ] . startsWith ( "client.log" ) ) . sort ( ( a , b ) => compare_file ( a [ 0 ] , b [ 0 ] ) ) ;
68+ if ( logFiles . length > 0 ) {
69+ const latestLogFile = logFiles [ logFiles . length - 1 ] [ 0 ] ;
70+ const uri = vscode . Uri . joinPath ( this . javaExtensionRoot , latestLogFile ) ;
71+ return await vscode . workspace . fs . readFile ( uri ) ;
72+ }
73+ }
74+ return undefined ;
75+ }
76+ }
77+ /**
78+ * filename: client.log.yyyy-mm-dd.r
79+ */
80+ function compare_file ( a : string , b : string ) {
81+ const dateA = a . slice ( 11 , 21 ) , dateB = b . slice ( 11 , 21 ) ;
82+ if ( dateA === dateB ) {
83+ if ( a . length > 22 && b . length > 22 ) {
84+ const extA = a . slice ( 22 ) , extB = b . slice ( 22 ) ;
85+ return parseInt ( extA ) - parseInt ( extB ) ;
86+ } else {
87+ return a . length - b . length ;
88+ }
89+ } else {
90+ return dateA < dateB ? - 1 : 1 ;
91+ }
92+ }
93+
94+ function parse ( rawLog : string ) {
95+ const SEP = / \r ? \n / ;
96+ const START = "{" ;
97+ const END = "}" ;
98+
99+ const ret = [ ] ;
100+
101+ let current : { [ key : string ] : string } | undefined = undefined ;
102+ for ( const line of rawLog . split ( SEP ) ) {
103+ if ( line === START ) {
104+ current = { } ;
105+ } else if ( line === END ) {
106+ if ( current !== undefined ) {
107+ ret . push ( current ) ;
108+ current = undefined ;
109+ }
110+ } else {
111+ if ( current !== undefined ) {
112+ const m = line . match ( / ^ \s * ( .* ) : \s [ ' " ] ( .* ?) [ ' " ] , ? $ / ) ;
113+ if ( m ) {
114+ current [ m [ 1 ] ] = m [ 2 ] ;
115+ }
116+ }
117+ }
118+ }
119+ return ret ;
120+ }
0 commit comments