5454import java .util .List ;
5555import java .util .Locale ;
5656import java .util .Map ;
57- import java .util .logging .Level ;
58- import java .util .logging .Logger ;
5957import java .util .regex .Pattern ;
6058import java .util .zip .ZipEntry ;
6159import java .util .zip .ZipInputStream ;
6260
61+ import java .util .logging .Level ;
62+ import java .util .logging .Logger ;
63+
6364/**
6465 * Utility to start and stop local Google Cloud Datastore process.
6566 */
6667public class LocalGcdHelper {
68+
6769 private static final Logger log = Logger .getLogger (LocalGcdHelper .class .getName ());
6870
6971 private final String projectId ;
7072 private Path gcdPath ;
71- private Process startProcess ;
7273 private ProcessStreamReader processReader ;
73- private ProcessErrorStreamReader processErrorReader ;
7474 private final int port ;
7575
7676 public static final String DEFAULT_PROJECT_ID = "projectid1" ;
@@ -179,134 +179,91 @@ private static Path executablePath(String cmd) {
179179 }
180180
181181 private static class ProcessStreamReader extends Thread {
182- private final BufferedReader reader ;
183-
184- ProcessStreamReader (InputStream inputStream ) {
185- super ("Local GCD InputStream reader" );
186- setDaemon (true );
187- reader = new BufferedReader (new InputStreamReader (inputStream ));
188- }
189-
190- void terminate () throws IOException {
191- reader .close ();
192- }
193-
194- @ Override
195- public void run () {
196- try {
197- while (!(reader .readLine () != null )) {
198- // consume line
199- }
200- } catch (IOException e ) {
201- // ignore
202- }
203- }
204-
205- public static ProcessStreamReader start (InputStream inputStream ) {
206- ProcessStreamReader thread = new ProcessStreamReader (inputStream );
207- thread .start ();
208- return thread ;
209- }
210- }
211-
212- private static class ProcessErrorStreamReader extends Thread {
213- private static final int LOG_LENGTH_LIMIT = 50000 ;
214- private static final String GCD_LOGGING_CLASS =
215- "com.google.apphosting.client.serviceapp.BaseApiServlet" ;
216182
183+ private final Process process ;
184+ private final BufferedReader reader ;
217185 private final BufferedReader errorReader ;
218- private String currentLog = null ;
219- private Level currentLogLevel = null ;
220186
221- ProcessErrorStreamReader (InputStream errorStream , String blockUntil ) throws IOException {
222- super ("Local GCD ErrorStream reader" );
187+ ProcessStreamReader (
188+ Process process , String blockUntil , boolean blockOnErrorStream ) throws IOException {
189+ super ("Local GCD InputStream reader" );
223190 setDaemon (true );
224- errorReader = new BufferedReader (new InputStreamReader (errorStream ));
191+ this .process = process ;
192+ reader = new BufferedReader (new InputStreamReader (process .getInputStream ()));
193+ errorReader = new BufferedReader (new InputStreamReader (process .getErrorStream ()));
225194 if (!Strings .isNullOrEmpty (blockUntil )) {
226195 String line ;
227196 do {
228- line = errorReader .readLine ();
197+ if (blockOnErrorStream ) {
198+ line = errorReader .readLine ();
199+ } else {
200+ line = reader .readLine ();
201+ }
229202 } while (line != null && !line .contains (blockUntil ));
230203 }
231204 }
232205
233- void terminate () throws IOException {
234- writeLog (currentLogLevel , currentLog );
235- errorReader .close ();
206+ void terminate () throws InterruptedException , IOException {
207+ process .destroy ();
208+ process .waitFor ();
209+ reader .close ();
236210 }
237211
238212 @ Override
239213 public void run () {
240214 try {
215+ boolean readerDone = false ;
241216 boolean errorReaderDone = false ;
242- String previousLine = "" ;
243- String currentLine = "" ;
244- while (!errorReaderDone ) {
245- previousLine = currentLine ;
246- currentLine = errorReader .readLine ();
247- if (currentLine == null ) {
248- errorReaderDone = true ;
249- } else {
250- processLogLine (previousLine , currentLine );
217+ String currentLog = null ;
218+ while (!readerDone || !errorReaderDone ) {
219+ if (!readerDone && reader .ready ()) {
220+ readerDone = reader .readLine () == null ;
221+ }
222+ if (!errorReaderDone && errorReader .ready ()) {
223+ String errorOutput = errorReader .readLine ();
224+ if (errorOutput == null ) {
225+ errorReaderDone = true ;
226+ } else {
227+ currentLog = processLog (errorOutput , currentLog );
228+ }
251229 }
252230 }
253231 } catch (IOException e ) {
254232 // ignore
255233 }
256234 }
257235
258- private void processLogLine (String previousLine , String currentLine ) {
259- // Each gcd log is two lines with the following format:
260- // [Date] [Time] [GCD_LOGGING_CLASS] [method]
261- // [LEVEL]: error message
262- // Exceptions and stack traces are included in gcd error stream, separated by a newline
263- Level nextLogLevel = getLevel (currentLine );
264- if (previousLine .contains (GCD_LOGGING_CLASS ) && nextLogLevel != null ) {
265- writeLog (currentLogLevel , currentLog );
266- if (currentLine .startsWith ("SEVERE: " )) {
267- // don't show duplicate error messages from gcd.sh (see issue #258)
268- currentLog = null ;
269- currentLogLevel = null ;
270- } else {
271- currentLog = "GCD" + currentLine .split (":" , 2 )[1 ] + System .getProperty ("line.separator" );
272- currentLogLevel = nextLogLevel ;
273- }
274- } else if (currentLog != null && currentLog .length () > LOG_LENGTH_LIMIT ) {
275- // log processing may be off, so drop some logs before the string becomes too big
276- currentLog = null ;
277- currentLogLevel = null ;
278- } else if (currentLog != null && isUsefulLogInfo (currentLine )) {
279- currentLog += currentLine + System .getProperty ("line.separator" );
280- }
236+ private static boolean isNewGcdLog (String line ) {
237+ return line .contains ("com.google.apphosting.client.serviceapp.BaseApiServlet" )
238+ && !line .trim ().startsWith ("at " );
281239 }
282240
283- private static void writeLog (Level level , String msg ) {
284- if (level != null && !Strings .isNullOrEmpty (msg )) {
285- log .log (level , msg .trim ());
286- }
287- }
288-
289- private static boolean isUsefulLogInfo (String line ) {
290- return !line .trim ().startsWith ("at " ) && !line .contains (GCD_LOGGING_CLASS );
291- }
292-
293- private static Level getLevel (String line ) {
294- try {
295- return Level .parse (line .split (":" )[0 ]);
296- } catch (IllegalArgumentException e ) {
297- return null ; // level wasn't supplied in this log line
241+ private static String processLog (String currentLine , String currentLog ) {
242+ if (isNewGcdLog (currentLine )) {
243+ if (currentLog != null ) {
244+ log .info (currentLog .trim ());
245+ }
246+ return "GCD " ;
247+ } else if (currentLine .startsWith ("SEVERE: " )) {
248+ return null ; // Don't show duplicate error messages from gcd.sh
249+ } else if (currentLog != null && currentLine .startsWith ("INFO: " )) {
250+ return currentLog + currentLine .substring ("INFO: " .length ()) + "\n " ;
251+ } else if (currentLog != null && !currentLine .trim ().startsWith ("at " )) {
252+ return currentLog + currentLine + "\n " ;
298253 }
254+ return currentLog ;
299255 }
300256
301- public static ProcessErrorStreamReader start (InputStream errorStream , String blockUntil )
302- throws IOException {
303- ProcessErrorStreamReader thread = new ProcessErrorStreamReader ( errorStream , blockUntil );
257+ public static ProcessStreamReader start (
258+ Process process , String blockUntil , boolean blockOnErrorStream ) throws IOException {
259+ ProcessStreamReader thread = new ProcessStreamReader ( process , blockUntil , blockOnErrorStream );
304260 thread .start ();
305261 return thread ;
306262 }
307263 }
308264
309265 private static class CommandWrapper {
266+
310267 private final List <String > prefix ;
311268 private List <String > command ;
312269 private String nullFilename ;
@@ -477,15 +434,12 @@ private void startGcd(Path executablePath) throws IOException, InterruptedExcept
477434 if (log .isLoggable (Level .FINE )) {
478435 log .log (Level .FINE , "Starting datastore emulator for the project: {0}" , projectId );
479436 }
480- startProcess =
481- CommandWrapper .create ()
482- .command (gcdAbsolutePath .toString (), "start" , "--testing" , "--allow_remote_shutdown" ,
483- "--port=" + Integer .toString (port ), projectId )
484- .directory (gcdPath )
485- .start ();
486- processReader = ProcessStreamReader .start (startProcess .getInputStream ());
487- processErrorReader = ProcessErrorStreamReader .start (
488- startProcess .getErrorStream (), "Dev App Server is now running" );
437+ Process startProcess = CommandWrapper .create ()
438+ .command (gcdAbsolutePath .toString (), "start" , "--testing" , "--allow_remote_shutdown" ,
439+ "--port=" + Integer .toString (port ), projectId )
440+ .directory (gcdPath )
441+ .start ();
442+ processReader = ProcessStreamReader .start (startProcess , "Dev App Server is now running" , true );
489443 }
490444
491445 private static String md5 (File gcdZipFile ) throws IOException {
@@ -541,9 +495,6 @@ public void stop() throws IOException, InterruptedException {
541495 sendQuitRequest (port );
542496 if (processReader != null ) {
543497 processReader .terminate ();
544- processErrorReader .terminate ();
545- startProcess .destroy ();
546- startProcess .waitFor ();
547498 }
548499 if (gcdPath != null ) {
549500 deleteRecurse (gcdPath );
@@ -555,6 +506,7 @@ private static void deleteRecurse(Path path) throws IOException {
555506 return ;
556507 }
557508 Files .walkFileTree (path , new SimpleFileVisitor <Path >() {
509+
558510 @ Override
559511 public FileVisitResult postVisitDirectory (Path dir , IOException exc ) throws IOException {
560512 Files .delete (dir );
@@ -569,7 +521,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
569521 });
570522 }
571523
572- public static LocalGcdHelper start (String projectId , int port )
524+ public static LocalGcdHelper start (String projectId , int port )
573525 throws IOException , InterruptedException {
574526 LocalGcdHelper helper = new LocalGcdHelper (projectId , port );
575527 helper .start ();
@@ -579,14 +531,15 @@ public static LocalGcdHelper start(String projectId, int port)
579531 public static void main (String ... args ) throws IOException , InterruptedException {
580532 Map <String , String > parsedArgs = parseArgs (args );
581533 String action = parsedArgs .get ("action" );
582- int port =
583- ( parsedArgs . get ( "port" ) == null ) ? DEFAULT_PORT : Integer .parseInt (parsedArgs .get ("port" ));
534+ int port = ( parsedArgs . get ( "port" ) == null ) ? DEFAULT_PORT
535+ : Integer .parseInt (parsedArgs .get ("port" ));
584536 switch (action ) {
585537 case "START" :
586538 if (!isActive (DEFAULT_PROJECT_ID , port )) {
587539 LocalGcdHelper helper = start (DEFAULT_PROJECT_ID , port );
588540 try (FileWriter writer = new FileWriter (".local_gcd_helper" )) {
589- writer .write (helper .gcdPath .toAbsolutePath ().toString () + System .lineSeparator ());
541+ writer .write (
542+ helper .gcdPath .toAbsolutePath ().toString () + System .lineSeparator ());
590543 writer .write (Integer .toString (port ));
591544 }
592545 }
0 commit comments