Skip to content

Commit 03dbc8c

Browse files
committed
Merge pull request #250 from ajkannan/forward-errors
Forward errors from gcd.sh
2 parents b0c8874 + 291486d commit 03dbc8c

File tree

1 file changed

+129
-35
lines changed

1 file changed

+129
-35
lines changed

gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java

Lines changed: 129 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.gcloud.datastore.testing;
1818

19+
import static com.google.common.base.MoreObjects.firstNonNull;
1920
import static java.nio.charset.StandardCharsets.UTF_8;
2021

2122
import com.google.common.base.Strings;
@@ -54,23 +55,23 @@
5455
import java.util.List;
5556
import java.util.Locale;
5657
import java.util.Map;
58+
import java.util.logging.Level;
59+
import java.util.logging.Logger;
5760
import java.util.regex.Pattern;
5861
import java.util.zip.ZipEntry;
5962
import java.util.zip.ZipInputStream;
6063

61-
import java.util.logging.Level;
62-
import java.util.logging.Logger;
63-
6464
/**
6565
* Utility to start and stop local Google Cloud Datastore process.
6666
*/
6767
public class LocalGcdHelper {
68-
6968
private static final Logger log = Logger.getLogger(LocalGcdHelper.class.getName());
7069

7170
private final String projectId;
7271
private Path gcdPath;
72+
private Process startProcess;
7373
private ProcessStreamReader processReader;
74+
private ProcessErrorStreamReader processErrorReader;
7475
private final int port;
7576

7677
public static final String DEFAULT_PROJECT_ID = "projectid1";
@@ -179,49 +180,139 @@ private static Path executablePath(String cmd) {
179180
}
180181

181182
private static class ProcessStreamReader extends Thread {
182-
183-
private final Process process;
184183
private final BufferedReader reader;
184+
private volatile boolean terminated;
185185

186-
ProcessStreamReader(Process process, String blockUntil) throws IOException {
186+
ProcessStreamReader(InputStream inputStream) {
187187
super("Local GCD InputStream reader");
188188
setDaemon(true);
189-
this.process = process;
190-
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
189+
reader = new BufferedReader(new InputStreamReader(inputStream));
190+
}
191+
192+
void terminate() throws IOException {
193+
terminated = true;
194+
reader.close();
195+
}
196+
197+
@Override
198+
public void run() {
199+
while (!terminated) {
200+
try {
201+
String line = reader.readLine();
202+
if (line == null) {
203+
terminated = true;
204+
}
205+
} catch (IOException e) {
206+
// ignore
207+
}
208+
}
209+
}
210+
211+
public static ProcessStreamReader start(InputStream inputStream) {
212+
ProcessStreamReader thread = new ProcessStreamReader(inputStream);
213+
thread.start();
214+
return thread;
215+
}
216+
}
217+
218+
private static class ProcessErrorStreamReader extends Thread {
219+
private static final int LOG_LENGTH_LIMIT = 50000;
220+
private static final String GCD_LOGGING_CLASS =
221+
"com.google.apphosting.client.serviceapp.BaseApiServlet";
222+
223+
private final BufferedReader errorReader;
224+
private StringBuilder currentLog;
225+
private Level currentLogLevel;
226+
private boolean collectionMode;
227+
private volatile boolean terminated;
228+
229+
ProcessErrorStreamReader(InputStream errorStream, String blockUntil) throws IOException {
230+
super("Local GCD ErrorStream reader");
231+
setDaemon(true);
232+
errorReader = new BufferedReader(new InputStreamReader(errorStream));
191233
if (!Strings.isNullOrEmpty(blockUntil)) {
192234
String line;
193235
do {
194-
line = reader.readLine();
236+
line = errorReader.readLine();
195237
} while (line != null && !line.contains(blockUntil));
196238
}
197239
}
198240

199-
void terminate() throws InterruptedException, IOException {
200-
process.destroy();
201-
process.waitFor();
202-
reader.close();
241+
void terminate() throws IOException {
242+
terminated = true;
243+
errorReader.close();
203244
}
204245

205246
@Override
206247
public void run() {
207-
try {
208-
while (reader.readLine() != null) {
209-
// consume line
248+
String previousLine = "";
249+
String nextLine = "";
250+
while (!terminated) {
251+
try {
252+
previousLine = nextLine;
253+
nextLine = errorReader.readLine();
254+
if (nextLine == null) {
255+
terminated = true;
256+
} else {
257+
processLogLine(previousLine, nextLine);
258+
}
259+
} catch (IOException e) {
260+
// ignore
261+
}
262+
}
263+
processLogLine(previousLine, firstNonNull(nextLine, ""));
264+
writeLog(currentLogLevel, currentLog);
265+
}
266+
267+
private void processLogLine(String previousLine, String nextLine) {
268+
// Each gcd log is two lines with the following format:
269+
// [Date] [Time] [GCD_LOGGING_CLASS] [method]
270+
// [LEVEL]: error message
271+
// Exceptions and stack traces are included in gcd error stream, separated by a newline
272+
Level nextLogLevel = getLevel(nextLine);
273+
if (nextLogLevel != null) {
274+
writeLog(currentLogLevel, currentLog);
275+
currentLog = new StringBuilder();
276+
currentLogLevel = nextLogLevel;
277+
collectionMode = previousLine.contains(GCD_LOGGING_CLASS);
278+
} else if (collectionMode) {
279+
if (currentLog.length() > LOG_LENGTH_LIMIT) {
280+
collectionMode = false;
281+
} else if (currentLog.length() == 0) {
282+
// strip level out of the line
283+
currentLog.append("GCD");
284+
currentLog.append(previousLine.split(":", 2)[1]);
285+
currentLog.append(System.getProperty("line.separator"));
286+
} else {
287+
currentLog.append(previousLine);
288+
currentLog.append(System.getProperty("line.separator"));
210289
}
211-
} catch (IOException e) {
212-
// ignore
213290
}
214291
}
215292

216-
public static ProcessStreamReader start(Process process, String blockUntil) throws IOException {
217-
ProcessStreamReader thread = new ProcessStreamReader(process, blockUntil);
293+
private static void writeLog(Level level, StringBuilder msg) {
294+
if (level != null && msg != null && msg.length() != 0) {
295+
log.log(level, msg.toString().trim());
296+
}
297+
}
298+
299+
private static Level getLevel(String line) {
300+
try {
301+
return Level.parse(line.split(":")[0]);
302+
} catch (IllegalArgumentException e) {
303+
return null; // level wasn't supplied in this log line
304+
}
305+
}
306+
307+
public static ProcessErrorStreamReader start(InputStream errorStream, String blockUntil)
308+
throws IOException {
309+
ProcessErrorStreamReader thread = new ProcessErrorStreamReader(errorStream, blockUntil);
218310
thread.start();
219311
return thread;
220312
}
221313
}
222314

223315
private static class CommandWrapper {
224-
225316
private final List<String> prefix;
226317
private List<String> command;
227318
private String nullFilename;
@@ -392,13 +483,15 @@ private void startGcd(Path executablePath) throws IOException, InterruptedExcept
392483
if (log.isLoggable(Level.FINE)) {
393484
log.log(Level.FINE, "Starting datastore emulator for the project: {0}", projectId);
394485
}
395-
Process startProcess = CommandWrapper.create()
396-
.command(gcdAbsolutePath.toString(), "start", "--testing", "--allow_remote_shutdown",
397-
"--port=" + Integer.toString(port), projectId)
398-
.directory(gcdPath)
399-
.redirectErrorStream()
400-
.start();
401-
processReader = ProcessStreamReader.start(startProcess, "Dev App Server is now running");
486+
startProcess =
487+
CommandWrapper.create()
488+
.command(gcdAbsolutePath.toString(), "start", "--testing", "--allow_remote_shutdown",
489+
"--port=" + Integer.toString(port), projectId)
490+
.directory(gcdPath)
491+
.start();
492+
processReader = ProcessStreamReader.start(startProcess.getInputStream());
493+
processErrorReader = ProcessErrorStreamReader.start(
494+
startProcess.getErrorStream(), "Dev App Server is now running");
402495
}
403496

404497
private static String md5(File gcdZipFile) throws IOException {
@@ -454,6 +547,9 @@ public void stop() throws IOException, InterruptedException {
454547
sendQuitRequest(port);
455548
if (processReader != null) {
456549
processReader.terminate();
550+
processErrorReader.terminate();
551+
startProcess.destroy();
552+
startProcess.waitFor();
457553
}
458554
if (gcdPath != null) {
459555
deleteRecurse(gcdPath);
@@ -465,7 +561,6 @@ private static void deleteRecurse(Path path) throws IOException {
465561
return;
466562
}
467563
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
468-
469564
@Override
470565
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
471566
Files.delete(dir);
@@ -480,7 +575,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
480575
});
481576
}
482577

483-
public static LocalGcdHelper start(String projectId, int port)
578+
public static LocalGcdHelper start(String projectId, int port)
484579
throws IOException, InterruptedException {
485580
LocalGcdHelper helper = new LocalGcdHelper(projectId, port);
486581
helper.start();
@@ -490,15 +585,14 @@ public static LocalGcdHelper start(String projectId, int port)
490585
public static void main(String... args) throws IOException, InterruptedException {
491586
Map<String, String> parsedArgs = parseArgs(args);
492587
String action = parsedArgs.get("action");
493-
int port = (parsedArgs.get("port") == null) ? DEFAULT_PORT
494-
: Integer.parseInt(parsedArgs.get("port"));
588+
int port =
589+
(parsedArgs.get("port") == null) ? DEFAULT_PORT : Integer.parseInt(parsedArgs.get("port"));
495590
switch (action) {
496591
case "START":
497592
if (!isActive(DEFAULT_PROJECT_ID, port)) {
498593
LocalGcdHelper helper = start(DEFAULT_PROJECT_ID, port);
499594
try (FileWriter writer = new FileWriter(".local_gcd_helper")) {
500-
writer.write(
501-
helper.gcdPath.toAbsolutePath().toString() + System.lineSeparator());
595+
writer.write(helper.gcdPath.toAbsolutePath().toString() + System.lineSeparator());
502596
writer.write(Integer.toString(port));
503597
}
504598
}

0 commit comments

Comments
 (0)