use github.com/golang/glog
diff --git a/Makefile b/Makefile
index 9f75864..6361ce4 100644
--- a/Makefile
+++ b/Makefile
@@ -51,13 +51,14 @@
 all: kati ckati $(CXX_TEST_EXES)
 
 kati: go_src_stamp
-	GOPATH=$$(pwd)/out go install github.com/google/kati/cmd/kati
+	GOPATH=$$(pwd)/out:$${GOPATH} go install github.com/google/kati/cmd/kati
 	cp out/bin/kati $@
 
 go_src_stamp: $(GO_SRCS) cmd/*/*.go
 	-rm -rf out/src/github.com/google/kati
 	mkdir -p out/src/github.com/google/kati
 	cp -a $(GO_SRCS) cmd out/src/github.com/google/kati
+	GOPATH=$$(pwd)/out:$${GOPATH} go get github.com/google/kati/cmd/kati
 	touch $@
 
 go_test: $(GO_SRCS)
diff --git a/ast.go b/ast.go
index ff5c833..1aeedb2 100644
--- a/ast.go
+++ b/ast.go
@@ -14,7 +14,11 @@
 
 package kati
 
-import "strings"
+import (
+	"strings"
+
+	"github.com/golang/glog"
+)
 
 type ast interface {
 	eval(*Evaluator) error
@@ -77,7 +81,7 @@
 }
 
 func (ast *assignAST) show() {
-	logf("%s %s %s %q", ast.opt, ast.lhs, ast.op, ast.rhs)
+	glog.Infof("%s %s %s %q", ast.opt, ast.lhs, ast.op, ast.rhs)
 }
 
 // maybeRuleAST is an ast for rule line.
@@ -96,7 +100,7 @@
 }
 
 func (ast *maybeRuleAST) show() {
-	logf("%s", ast.expr)
+	glog.Info(ast.expr)
 }
 
 type commandAST struct {
@@ -109,7 +113,7 @@
 }
 
 func (ast *commandAST) show() {
-	logf("\t%s", strings.Replace(ast.cmd, "\n", `\n`, -1))
+	glog.Infof("\t%s", strings.Replace(ast.cmd, "\n", `\n`, -1))
 }
 
 type includeAST struct {
@@ -123,7 +127,7 @@
 }
 
 func (ast *includeAST) show() {
-	logf("include %s", ast.expr)
+	glog.Infof("include %s", ast.expr)
 }
 
 type ifAST struct {
@@ -141,7 +145,7 @@
 
 func (ast *ifAST) show() {
 	// TODO
-	logf("if")
+	glog.Info("if")
 }
 
 type exportAST struct {
@@ -156,5 +160,5 @@
 
 func (ast *exportAST) show() {
 	// TODO
-	logf("export")
+	glog.Info("export")
 }
diff --git a/cmd/kati/main.go b/cmd/kati/main.go
index 81e69db..5220405 100644
--- a/cmd/kati/main.go
+++ b/cmd/kati/main.go
@@ -27,6 +27,7 @@
 	"text/template"
 	"time"
 
+	"github.com/golang/glog"
 	"github.com/google/kati"
 )
 
@@ -89,7 +90,6 @@
 		"space separated leaf names for find cache.")
 	flag.StringVar(&shellDate, "shell_date", "", "specify $(shell date) time as "+shellDateTimeformat)
 
-	flag.BoolVar(&kati.LogFlag, "kati_log", false, "Verbose kati specific log")
 	flag.BoolVar(&kati.StatsFlag, "kati_stats", false, "Show a bunch of statistics")
 	flag.BoolVar(&kati.PeriodicStatsFlag, "kati_periodic_stats", false, "Show a bunch of periodic statistics")
 	flag.BoolVar(&kati.EvalStatsFlag, "kati_eval_stats", false, "Show eval statistics")
@@ -221,6 +221,7 @@
 }
 
 func katiMain(args []string) error {
+	defer glog.Flush()
 	if cpuprofile != "" {
 		f, err := os.Create(cpuprofile)
 		if err != nil {
diff --git a/dep.go b/dep.go
index e5b3b35..f336d8e 100644
--- a/dep.go
+++ b/dep.go
@@ -18,6 +18,8 @@
 	"fmt"
 	"path/filepath"
 	"strings"
+
+	"github.com/golang/glog"
 )
 
 // DepNode represents a makefile rule for an output.
@@ -78,9 +80,9 @@
 }
 
 func (rt *ruleTrie) add(name string, r *rule) {
-	logf("rule trie: add %q %v %s", name, r.outputPatterns[0], r)
+	glog.V(1).Infof("rule trie: add %q %v %s", name, r.outputPatterns[0], r)
 	if name == "" || name[0] == '%' {
-		logf("rule trie: add entry %q %v %s", name, r.outputPatterns[0], r)
+		glog.V(1).Infof("rule trie: add entry %q %v %s", name, r.outputPatterns[0], r)
 		rt.rules = append(rt.rules, ruleTrieEntry{
 			rule:   r,
 			suffix: name,
@@ -96,7 +98,7 @@
 }
 
 func (rt *ruleTrie) lookup(name string) []*rule {
-	logf("rule trie: lookup %q", name)
+	glog.V(1).Infof("rule trie: lookup %q", name)
 	if rt == nil {
 		return nil
 	}
@@ -110,7 +112,7 @@
 		return rules
 	}
 	rules = append(rules, rt.children[name[0]].lookup(name[1:])...)
-	logf("rule trie: lookup %q => %v", name, rules)
+	glog.V(1).Infof("rule trie: lookup %q => %v", name, rules)
 	return rules
 }
 
@@ -163,8 +165,8 @@
 		// TODO(ukai): should return error?
 		panic(fmt.Sprintf("FIXME: Implicit rule should have only one output but %q", outputs))
 	}
-	logf("merge? %q", db.ruleVars)
-	logf("merge? %q", outputs[0])
+	glog.V(1).Infof("merge? %q", db.ruleVars)
+	glog.V(1).Infof("merge? %q", outputs[0])
 	ivars, present := db.ruleVars[outputs[0]]
 	if !present {
 		return vars
@@ -172,7 +174,7 @@
 	if vars == nil {
 		return ivars
 	}
-	logf("merge!")
+	glog.V(1).Info("merge!")
 	v := make(Vars)
 	v.Merge(ivars)
 	v.Merge(vars)
@@ -197,10 +199,10 @@
 	for i := len(irules) - 1; i >= 0; i-- {
 		irule := irules[i]
 		if !db.canPickImplicitRule(irule, output) {
-			logf("ignore implicit rule %q %s", output, irule)
+			glog.Infof("ignore implicit rule %q %s", output, irule)
 			continue
 		}
-		logf("pick implicit rule %q => %q %s", output, irule.outputPatterns, irule)
+		glog.Infof("pick implicit rule %q => %q %s", output, irule.outputPatterns, irule)
 		db.pickImplicitRuleCnt++
 		if r != nil {
 			ir := &rule{}
@@ -261,7 +263,7 @@
 }
 
 func (db *depBuilder) buildPlan(output string, neededBy string, tsvs Vars) (*DepNode, error) {
-	logf("Evaluating command: %s", output)
+	glog.V(1).Infof("Evaluating command: %s", output)
 	db.nodeCnt++
 	if db.nodeCnt%100 == 0 {
 		db.reportStats()
@@ -319,7 +321,7 @@
 	}
 
 	var actualInputs []string
-	logf("Evaluating command: %s inputs:%q", output, rule.inputs)
+	glog.Infof("Evaluating command: %s inputs:%q", output, rule.inputs)
 	for _, input := range rule.inputs {
 		if len(rule.outputPatterns) > 0 {
 			if len(rule.outputPatterns) > 1 {
@@ -361,7 +363,7 @@
 	n.ActualInputs = actualInputs
 	n.TargetSpecificVars = make(Vars)
 	for k, v := range tsvs {
-		logf("output=%s tsv %s=%s", output, k, v)
+		glog.Infof("output=%s tsv %s=%s", output, k, v)
 		n.TargetSpecificVars[k] = v
 	}
 	n.Filename = rule.filename
@@ -484,7 +486,7 @@
 }
 
 func (db *depBuilder) reportStats() {
-	if !LogFlag && !PeriodicStatsFlag {
+	if !PeriodicStatsFlag {
 		return
 	}
 
diff --git a/depgraph.go b/depgraph.go
index 2e921fc..95fd079 100644
--- a/depgraph.go
+++ b/depgraph.go
@@ -20,6 +20,8 @@
 	"io/ioutil"
 	"strings"
 	"time"
+
+	"github.com/golang/glog"
 )
 
 // DepGraph represents rules defined in makefiles.
@@ -59,7 +61,7 @@
 	}
 	mk, err := defaultMakefile()
 	if err != nil {
-		logf("default makefile: %v", err)
+		glog.Warningf("default makefile: %v", err)
 	}
 	return LoadReq{
 		Makefile:        mk,
@@ -71,7 +73,7 @@
 func initVars(vars Vars, kvlist []string, origin string) error {
 	for _, v := range kvlist {
 		kv := strings.SplitN(v, "=", 2)
-		logf("%s var %q", origin, v)
+		glog.V(1).Infof("%s var %q", origin, v)
 		if len(kv) < 2 {
 			return fmt.Errorf("A weird %s variable %q", origin, kv)
 		}
diff --git a/eval.go b/eval.go
index 00c9ee7..421397a 100644
--- a/eval.go
+++ b/eval.go
@@ -22,6 +22,8 @@
 	"path/filepath"
 	"strings"
 	"sync"
+
+	"github.com/golang/glog"
 )
 
 type fileState int
@@ -197,8 +199,8 @@
 	if err != nil {
 		return err
 	}
-	if LogFlag {
-		logf("ASSIGN: %s=%q (flavor:%q)", lhs, rhs, rhs.Flavor())
+	if glog.V(1) {
+		glog.Infof("ASSIGN: %s=%q (flavor:%q)", lhs, rhs, rhs.Flavor())
 	}
 	if lhs == "" {
 		return ast.errorf("*** empty variable name.")
@@ -243,8 +245,8 @@
 	if err != nil {
 		return err
 	}
-	if LogFlag {
-		logf("rule outputs:%q assign:%q%s%q (flavor:%q)", output, lhs, assign.op, rhs, rhs.Flavor())
+	if glog.V(1) {
+		glog.Infof("rule outputs:%q assign:%q%s%q (flavor:%q)", output, lhs, assign.op, rhs, rhs.Flavor())
 	}
 	vars.Assign(lhs, &targetSpecificVar{v: rhs, op: assign.op})
 	ev.currentScope = nil
@@ -255,7 +257,7 @@
 	ev.lastRule = nil
 	ev.srcpos = ast.srcpos
 
-	logf("maybe rule %s: %q assign:%v", ev.srcpos, ast.expr, ast.assign)
+	glog.V(1).Infof("maybe rule %s: %q assign:%v", ev.srcpos, ast.expr, ast.assign)
 
 	abuf := newEbuf()
 	aexpr := toExpr(ast.expr)
@@ -303,15 +305,15 @@
 		return ast.error(err)
 	}
 	abuf.release()
-	if LogFlag {
-		logf("rule %q assign:%v rhs:%v=> outputs:%q, inputs:%q", ast.expr, ast.assign, rhs, r.outputs, r.inputs)
+	if glog.V(1) {
+		glog.Infof("rule %q assign:%v rhs:%v=> outputs:%q, inputs:%q", ast.expr, ast.assign, rhs, r.outputs, r.inputs)
 	}
 
 	// TODO: Pretty print.
-	//logf("RULE: %s=%s (%d commands)", lhs, rhs, len(cmds))
+	// glog.V(1).Infof("RULE: %s=%s (%d commands)", lhs, rhs, len(cmds))
 
 	if assign != nil {
-		logf("target specific var: %#v", assign)
+		glog.V(1).Infof("target specific var: %#v", assign)
 		for _, output := range r.outputs {
 			ev.setTargetSpecificVar(assign, output)
 		}
@@ -324,8 +326,8 @@
 	if semi != nil {
 		r.cmds = append(r.cmds, string(semi))
 	}
-	if LogFlag {
-		logf("rule outputs:%q cmds:%q", r.outputs, r.cmds)
+	if glog.V(1) {
+		glog.Infof("rule outputs:%q cmds:%q", r.outputs, r.cmds)
 	}
 	ev.lastRule = r
 	ev.outRules = append(ev.outRules, r)
@@ -432,7 +434,7 @@
 	ev.lastRule = nil
 	ev.srcpos = ast.srcpos
 
-	logf("%s include %q", ev.srcpos, ast.expr)
+	glog.Infof("%s include %q", ev.srcpos, ast.expr)
 	v, _, err := parseExpr([]byte(ast.expr), nil, parseOp{})
 	if err != nil {
 		return ast.errorf("parse failed: %q: %v", ast.expr, err)
@@ -507,8 +509,8 @@
 		val := buf.Len()
 		buf.release()
 		isTrue = (val > 0) == (iast.op == "ifdef")
-		if LogFlag {
-			logf("%s lhs=%q value=%q => %t", iast.op, iast.lhs, value, isTrue)
+		if glog.V(1) {
+			glog.Infof("%s lhs=%q value=%q => %t", iast.op, iast.lhs, value, isTrue)
 		}
 	case "ifeq", "ifneq":
 		lexpr := iast.lhs
@@ -522,8 +524,8 @@
 		rhs := string(params[1])
 		buf.release()
 		isTrue = (lhs == rhs) == (iast.op == "ifeq")
-		if LogFlag {
-			logf("%s lhs=%q %q rhs=%q %q => %t", iast.op, iast.lhs, lhs, iast.rhs, rhs, isTrue)
+		if glog.V(1) {
+			glog.Infof("%s lhs=%q %q rhs=%q %q => %t", iast.op, iast.lhs, lhs, iast.rhs, rhs, isTrue)
 		}
 	default:
 		return iast.errorf("unknown if statement: %q", iast.op)
diff --git a/evalcmd.go b/evalcmd.go
index 14c06e5..4c65ec3 100644
--- a/evalcmd.go
+++ b/evalcmd.go
@@ -19,6 +19,8 @@
 	"os/exec"
 	"strings"
 	"sync"
+
+	"github.com/golang/glog"
 )
 
 type execContext struct {
@@ -246,7 +248,7 @@
 	}
 	cmds := buf.String()
 	buf.release()
-	logf("evalcmd: %q => %q", r.cmd, cmds)
+	glog.V(1).Infof("evalcmd: %q => %q", r.cmd, cmds)
 	var runners []runner
 	for _, cmd := range strings.Split(cmds, "\n") {
 		if len(runners) > 0 && strings.HasSuffix(runners[len(runners)-1].cmd, "\\") {
@@ -263,10 +265,12 @@
 	if r.echo || DryRunFlag {
 		fmt.Printf("%s\n", r.cmd)
 	}
+	s := cmdline(r.cmd)
+	glog.Infof("sh:%q", s)
 	if DryRunFlag {
 		return nil
 	}
-	args := []string{r.shell, "-c", cmdline(r.cmd)}
+	args := []string{r.shell, "-c", s}
 	cmd := exec.Cmd{
 		Path: args[0],
 		Args: args,
@@ -296,12 +300,12 @@
 		restore := ctx.ev.vars.save(k)
 		defer restore()
 		ctx.ev.vars[k] = v
-		logf("set tsv: %s=%s", k, v)
+		glog.Infof("set tsv: %s=%s", k, v)
 	}
 
 	ctx.ev.filename = n.Filename
 	ctx.ev.lineno = n.Lineno
-	logf("Building: %s cmds:%q", n.Output, n.Cmds)
+	glog.Infof("Building: %s cmds:%q", n.Output, n.Cmds)
 	r := runner{
 		output: n.Output,
 		echo:   true,
diff --git a/exec.go b/exec.go
index d3f8e05..712b917 100644
--- a/exec.go
+++ b/exec.go
@@ -18,6 +18,8 @@
 	"fmt"
 	"os"
 	"time"
+
+	"github.com/golang/glog"
 )
 
 // Executor manages execution of makefile rules.
@@ -44,7 +46,7 @@
 func (ex *Executor) makeJobs(n *DepNode, neededBy *job) error {
 	output, _ := existsInVPATH(ex.ctx.ev, n.Output)
 	if neededBy != nil {
-		logf("MakeJob: %s for %s", output, neededBy.n.Output)
+		glog.V(1).Infof("MakeJob: %s for %s", output, neededBy.n.Output)
 	}
 	n.Output = output
 	ex.buildCnt++
@@ -63,7 +65,7 @@
 				neededBy.numDeps--
 			}
 		} else {
-			logf("%s already done: %d", j.n.Output, j.outputTs)
+			glog.Infof("%s already done: %d", j.n.Output, j.outputTs)
 			if neededBy != nil {
 				ex.wm.ReportNewDep(j, neededBy)
 			}
@@ -97,7 +99,7 @@
 		}
 		deps = append(deps, d)
 	}
-	logf("new: %s (%d)", j.n.Output, j.numDeps)
+	glog.V(1).Infof("new: %s (%d)", j.n.Output, j.numDeps)
 
 	for _, d := range deps {
 		ex.trace = append(ex.trace, d.Output)
@@ -113,7 +115,7 @@
 }
 
 func (ex *Executor) reportStats() {
-	if !LogFlag && !PeriodicStatsFlag {
+	if !PeriodicStatsFlag {
 		return
 	}
 
diff --git a/expr.go b/expr.go
index b7ee6c4..79978b6 100644
--- a/expr.go
+++ b/expr.go
@@ -22,6 +22,8 @@
 	"regexp"
 	"strconv"
 	"strings"
+
+	"github.com/golang/glog"
 )
 
 var (
@@ -398,7 +400,7 @@
 	}
 	exp = appendStr(exp, in[b:i], op.alloc)
 	if i == len(in) && term != nil {
-		logf("parse: unexpected end of input: %q %d [%q]", in, i, term)
+		glog.Warningf("parse: unexpected end of input: %q %d [%q]", in, i, term)
 		return exp, i, errEndOfInput
 	}
 	return compactExpr(exp), i, nil
diff --git a/flags.go b/flags.go
index bf51d72..aa119eb 100644
--- a/flags.go
+++ b/flags.go
@@ -16,7 +16,6 @@
 
 // Flags to control kati.
 var (
-	LogFlag           bool
 	StatsFlag         bool
 	PeriodicStatsFlag bool
 	EvalStatsFlag     bool
diff --git a/func.go b/func.go
index 5336914..1570ea9 100644
--- a/func.go
+++ b/func.go
@@ -25,6 +25,8 @@
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/golang/glog"
 )
 
 // mkFunc is a make function.
@@ -181,7 +183,7 @@
 	from := fargs[0]
 	to := fargs[1]
 	text := fargs[2]
-	logf("subst from:%q to:%q text:%q", from, to, text)
+	glog.V(1).Infof("subst from:%q to:%q text:%q", from, to, text)
 	if len(from) == 0 {
 		w.Write(text)
 		w.Write(to)
@@ -786,12 +788,12 @@
 		name := string(word)
 		name, err := filepath.Abs(name)
 		if err != nil {
-			logf("abs: %v", err)
+			glog.Warningf("abs %q: %v", name, err)
 			continue
 		}
 		name, err = filepath.EvalSymlinks(name)
 		if err != nil {
-			logf("realpath: %v", err)
+			glog.Warningf("realpath %q: %v", name, err)
 			continue
 		}
 		w.writeWordString(name)
@@ -819,7 +821,7 @@
 		name := string(word)
 		name, err := filepath.Abs(name)
 		if err != nil {
-			logf("abs: %v", err)
+			glog.Warningf("abs %q: %v", name, err)
 			continue
 		}
 		w.writeWordString(name)
@@ -925,7 +927,7 @@
 	if !bytes.HasPrefix(s, []byte("echo $((")) || s[len(s)-1] != ')' {
 		return false
 	}
-	logf("has no IO - evaluate now: %s", s)
+	glog.Infof("has no IO - evaluate now: %s", s)
 	return true
 }
 
@@ -956,8 +958,8 @@
 		return err
 	}
 	cmdline := []string{shellVar, "-c", arg}
-	if LogFlag {
-		logf("shell %q", cmdline)
+	if glog.V(1) {
+		glog.Infof("shell %q", cmdline)
 	}
 	cmd := exec.Cmd{
 		Path:   cmdline[0],
@@ -968,7 +970,7 @@
 	out, err := cmd.Output()
 	shellStats.add(time.Since(te.t))
 	if err != nil {
-		logf("$(shell %q) failed: %q", arg, err)
+		glog.Warningf("$(shell %q) failed: %q", arg, err)
 	}
 	w.Write(formatCommandOutput(out))
 	traceEvent.end(te)
@@ -994,11 +996,11 @@
 		// hack for android
 		for _, sb := range shBuiltins {
 			if v, ok := matchExpr(exp, sb.pattern); ok {
-				logf("shell compact apply %s for %s", sb.name, exp)
+				glog.Infof("shell compact apply %s for %s", sb.name, exp)
 				return sb.compact(f, v)
 			}
 		}
-		logf("shell compact no match: %s", exp)
+		glog.V(1).Infof("shell compact no match: %s", exp)
 	}
 	return f
 }
@@ -1017,8 +1019,8 @@
 	varname := fargs[0]
 	variable := string(varname)
 	te := traceEvent.begin("call", literal(variable), traceEventMain)
-	if LogFlag {
-		logf("call %q variable %q", f.args[1], variable)
+	if glog.V(1) {
+		glog.Infof("call %q variable %q", f.args[1], variable)
 	}
 	v := ev.LookupVar(variable)
 	// Evalualte all arguments first before we modify the table.
@@ -1032,15 +1034,15 @@
 	for i, arg := range fargs[1:] {
 		// f.args[2]=>args[1] will be $1.
 		args = append(args, tmpval(arg))
-		if LogFlag {
-			logf("call $%d: %q=>%q", i+1, arg, fargs[i+1])
+		if glog.V(1) {
+			glog.Infof("call $%d: %q=>%q", i+1, arg, fargs[i+1])
 		}
 	}
 	oldParams := ev.paramVars
 	ev.paramVars = args
 
 	var buf bytes.Buffer
-	if LogFlag {
+	if glog.V(1) {
 		w = &ssvWriter{Writer: io.MultiWriter(w, &buf)}
 	}
 	err = v.Eval(w, ev)
@@ -1049,8 +1051,8 @@
 	}
 	ev.paramVars = oldParams
 	traceEvent.end(te)
-	if LogFlag {
-		logf("call %q variable %q return %q", f.args[1], variable, buf.Bytes())
+	if glog.V(1) {
+		glog.Infof("call %q variable %q return %q", f.args[1], variable, buf.Bytes())
 	}
 	abuf.release()
 	return nil
@@ -1091,7 +1093,7 @@
 		return err
 	}
 	s := abuf.Bytes()
-	logf("eval %v=>%q at %s", f.args[1], s, ev.srcpos)
+	glog.V(1).Infof("eval %v=>%q at %s", f.args[1], s, ev.srcpos)
 	mk, err := parseMakefileBytes(trimSpaceBytes(s), ev.srcpos)
 	if err != nil {
 		return ev.errorf("%v", err)
@@ -1127,7 +1129,7 @@
 					rhs = append(rhs, rhsprefix)
 				}
 				rhs = append(rhs, arg[1:]...)
-				logf("eval assign %#v => lhs:%q op:%q rhs:%#v", f, lhs, op, rhs)
+				glog.V(1).Infof("eval assign %#v => lhs:%q op:%q rhs:%#v", f, lhs, op, rhs)
 				return &funcEvalAssign{
 					lhs: lhs,
 					op:  op,
@@ -1225,7 +1227,7 @@
 		return err
 	}
 	rhs := trimLeftSpaceBytes(abuf.Bytes())
-	logf("evalAssign: lhs=%q rhs=%s %q", f.lhs, f.rhs, rhs)
+	glog.V(1).Infof("evalAssign: lhs=%q rhs=%s %q", f.lhs, f.rhs, rhs)
 	var rvalue Var
 	switch f.op {
 	case ":=":
@@ -1261,8 +1263,8 @@
 		}
 		rvalue = &recursiveVar{expr: tmpval(rhs), origin: "file"}
 	}
-	if LogFlag {
-		logf("Eval ASSIGN: %s=%q (flavor:%q)", f.lhs, rvalue, rvalue.Flavor())
+	if glog.V(1) {
+		glog.Infof("Eval ASSIGN: %s=%q (flavor:%q)", f.lhs, rvalue, rvalue.Flavor())
 	}
 	ev.outVars.Assign(f.lhs, rvalue)
 	return nil
diff --git a/log.go b/log.go
index 29609e7..4b3098f 100644
--- a/log.go
+++ b/log.go
@@ -15,35 +15,17 @@
 package kati
 
 import (
-	"bytes"
 	"fmt"
-	"sync"
+
+	"github.com/golang/glog"
 )
 
-var logMu sync.Mutex
-
-func logAlways(f string, a ...interface{}) {
-	var buf bytes.Buffer
-	buf.WriteString("*kati*: ")
-	buf.WriteString(f)
-	buf.WriteByte('\n')
-	logMu.Lock()
-	fmt.Printf(buf.String(), a...)
-	logMu.Unlock()
-}
-
 func logStats(f string, a ...interface{}) {
-	if !LogFlag && !StatsFlag {
+	// TODO(ukai): vmodule?
+	if !StatsFlag {
 		return
 	}
-	logAlways(f, a...)
-}
-
-func logf(f string, a ...interface{}) {
-	if !LogFlag {
-		return
-	}
-	logAlways(f, a...)
+	glog.Infof(f, a...)
 }
 
 func warn(loc srcpos, f string, a ...interface{}) {
diff --git a/parser.go b/parser.go
index 4d54844..d21ea5c 100644
--- a/parser.go
+++ b/parser.go
@@ -30,6 +30,8 @@
 	"strings"
 	"sync"
 	"time"
+
+	"github.com/golang/glog"
 )
 
 type makefile struct {
@@ -197,7 +199,7 @@
 			lhs, op = line[:sep-1], line[sep-1:sep+1]
 		}
 	}
-	logf("parseAssign %s op:%q opt:%s", line, op, p.defOpt)
+	glog.V(1).Infof("parseAssign %s op:%q opt:%s", line, op, p.defOpt)
 	lhs = trimSpaceBytes(lhs)
 	rhs = trimLeftSpaceBytes(rhs)
 	aast, err := newAssignAST(p, lhs, rhs, string(op))
@@ -279,7 +281,7 @@
 		semi:   semi,
 	}
 	rast.srcpos = p.srcpos()
-	logf("stmt: %#v", rast)
+	glog.V(1).Infof("stmt: %#v", rast)
 	p.addStatement(rast)
 }
 
@@ -339,11 +341,11 @@
 	}
 	if s[0] == '(' {
 		in := s[1:]
-		logf("parseEq ( %q )", in)
+		glog.V(1).Infof("parseEq ( %q )", in)
 		term := []byte{','}
 		v, n, err := parseExpr(in, term, parseOp{matchParen: true})
 		if err != nil {
-			logf("parse eq: %q: %v", in, err)
+			glog.V(1).Infof("parse eq: %q: %v", in, err)
 			return "", "", nil, false
 		}
 		lhs := v.String()
@@ -353,7 +355,7 @@
 		in = in[n:]
 		v, n, err = parseExpr(in, term, parseOp{matchParen: true})
 		if err != nil {
-			logf("parse eq 2nd: %q: %v", in, err)
+			glog.V(1).Infof("parse eq 2nd: %q: %v", in, err)
 			return "", "", nil, false
 		}
 		rhs := v.String()
@@ -371,7 +373,7 @@
 		return
 	}
 	if len(extra) > 0 {
-		logf("extra %q", extra)
+		glog.V(1).Infof("extra %q", extra)
 		p.err = p.srcpos().errorf(`extraneous text after %q directive`, op)
 		return
 	}
@@ -532,7 +534,7 @@
 	defineDirective := map[string]directiveFunc{
 		"define": defineDirective,
 	}
-	logf("override define? %q", data)
+	glog.V(1).Infof("override define? %q", data)
 	if p.handleDirective(data, defineDirective) {
 		return
 	}
@@ -564,7 +566,7 @@
 		export: export,
 	}
 	east.srcpos = p.srcpos()
-	logf("export %v", east)
+	glog.V(1).Infof("export %v", east)
 	p.addStatement(east)
 	return hasEqual
 }
@@ -574,7 +576,7 @@
 	defineDirective := map[string]directiveFunc{
 		"define": defineDirective,
 	}
-	logf("export define? %q", data)
+	glog.V(1).Infof("export define? %q", data)
 	if p.handleDirective(data, defineDirective) {
 		return
 	}
@@ -596,8 +598,8 @@
 func (p *parser) parse() (mk makefile, err error) {
 	for !p.done {
 		line := p.readLine()
-		if LogFlag {
-			logf("%s: %q", p.srcpos(), line)
+		if glog.V(1) {
+			glog.Infof("%s: %q", p.srcpos(), line)
 		}
 		if p.defineVar != nil {
 			p.processDefine(line)
@@ -628,8 +630,8 @@
 	if len(cline) == 0 {
 		return
 	}
-	if LogFlag {
-		logf("concatline:%q", cline)
+	if glog.V(1) {
+		glog.Infof("concatline:%q", cline)
 	}
 	var dline []byte
 	cline, _ = removeComment(cline)
@@ -638,22 +640,22 @@
 	if len(dline) == 0 {
 		return
 	}
-	if LogFlag {
-		logf("directive?: %q", dline)
+	if glog.V(1) {
+		glog.Infof("directive?: %q", dline)
 	}
 	if p.handleDirective(dline, makeDirectives) {
 		return
 	}
-	if LogFlag {
-		logf("rule or assign?: %q", line)
+	if glog.V(1) {
+		glog.Infof("rule or assign?: %q", line)
 	}
 	p.handleRuleOrAssign(line)
 }
 
 func (p *parser) processDefine(line []byte) {
 	line = concatline(line)
-	if LogFlag {
-		logf("concatline:%q", line)
+	if glog.V(1) {
+		glog.Infof("concatline:%q", line)
 	}
 	if !p.isEndef(line) {
 		if p.inDef != nil {
@@ -665,7 +667,7 @@
 		}
 		return
 	}
-	logf("multilineAssign %q %q", p.defineVar, p.inDef)
+	glog.V(1).Infof("multilineAssign %q %q", p.defineVar, p.inDef)
 	aast, err := newAssignAST(p, p.defineVar, p.inDef, "=")
 	if err != nil {
 		p.err = p.srcpos().errorf("assign error %q=%q: %v", p.defineVar, p.inDef, err)
@@ -753,16 +755,16 @@
 }
 
 func (mc *makefileCacheT) parse(filename string) (makefile, [sha1.Size]byte, error) {
-	logf("parse Makefile %q", filename)
+	glog.Infof("parse Makefile %q", filename)
 	mk, hash, ok, err := makefileCache.lookup(filename)
 	if ok {
-		if LogFlag {
-			logf("makefile cache hit for %q", filename)
+		if glog.V(1) {
+			glog.Infof("makefile cache hit for %q", filename)
 		}
 		return mk, hash, err
 	}
-	if LogFlag {
-		logf("reading makefile %q", filename)
+	if glog.V(1) {
+		glog.Infof("reading makefile %q", filename)
 	}
 	c, err := ioutil.ReadFile(filename)
 	if err != nil {
diff --git a/pathutil.go b/pathutil.go
index 591abb2..1653b3e 100644
--- a/pathutil.go
+++ b/pathutil.go
@@ -26,6 +26,8 @@
 	"strings"
 	"sync"
 	"time"
+
+	"github.com/golang/glog"
 )
 
 type wildcardCacheT struct {
@@ -207,7 +209,7 @@
 }
 
 func (c *androidFindCacheT) start(prunes, leafNames []string) {
-	logf("find cache init: prunes=%q leafNames=%q", prunes, leafNames)
+	glog.Infof("find cache init: prunes=%q leafNames=%q", prunes, leafNames)
 	te := traceEvent.begin("findcache", literal("init"), traceEventFindCache)
 	defer func() {
 		traceEvent.end(te)
@@ -229,7 +231,7 @@
 					if info.IsDir() {
 						for _, prune := range prunes {
 							if info.Name() == prune {
-								logf("find cache prune: %s", path)
+								glog.V(1).Infof("find cache prune: %s", path)
 								return filepath.SkipDir
 							}
 						}
@@ -240,7 +242,7 @@
 					}
 					for _, leaf := range leafNames {
 						if info.Name() == leaf {
-							logf("find cache leaf: %s", path)
+							glog.V(1).Infof("find cache leaf: %s", path)
 							leafch <- fileInfo{
 								path: path,
 								mode: info.Mode(),
@@ -251,7 +253,7 @@
 					return nil
 				})
 				if err != nil && err != filepath.SkipDir {
-					logf("error in adnroid find cache: %v", err)
+					glog.Warningf("error in adnroid find cache: %v", err)
 					close(c.filesch)
 					close(c.leavesch)
 					return
@@ -283,8 +285,11 @@
 		c.leavesch <- leaves
 		traceEvent.end(leavesTe)
 		logStats("%d leaves %d dirs in find cache", nfiles, len(dirs))
+		if !glog.V(1) {
+			return
+		}
 		for i, leaf := range leaves {
-			logf("android findleaves cache: %d: %s %v", i, leaf.path, leaf.mode)
+			glog.Infof("android findleaves cache: %d: %s %v", i, leaf.path, leaf.mode)
 		}
 	}()
 
@@ -298,21 +303,24 @@
 		c.filesch <- files
 		traceEvent.end(filesTe)
 		logStats("%d files in find cache", len(files))
+		if !glog.V(1) {
+			return
+		}
 		for i, fi := range files {
-			logf("android find cache: %d: %s %v", i, fi.path, fi.mode)
+			glog.Infof("android find cache: %d: %s %v", i, fi.path, fi.mode)
 		}
 	}()
 
 	curdir, err := os.Open(".")
 	if err != nil {
-		logf("open . failed: %v", err)
+		glog.Warningf("open . failed: %v", err)
 		close(c.filesch)
 		close(c.leavesch)
 		return
 	}
 	names, err := curdir.Readdirnames(-1)
 	if err != nil {
-		logf("readdir . failed: %v", err)
+		glog.Warningf("readdir . failed: %v", err)
 		close(c.filesch)
 		close(c.leavesch)
 		return
@@ -365,7 +373,7 @@
 	i := sort.Search(len(c.files), func(i int) bool {
 		return c.files[i].path >= dir
 	})
-	logf("android find in dir cache: %s i=%d/%d", dir, i, len(c.files))
+	glog.V(1).Infof("android find in dir cache: %s i=%d/%d", dir, i, len(c.files))
 	start := i
 	var skipdirs []string
 Loop:
@@ -378,7 +386,7 @@
 			continue
 		}
 		if !strings.HasPrefix(c.files[i].path, dir) {
-			logf("android find in dir cache: %s end=%d/%d", dir, i, len(c.files))
+			glog.V(1).Infof("android find in dir cache: %s end=%d/%d", dir, i, len(c.files))
 			return nil
 		}
 		if !strings.HasPrefix(c.files[i].path, dir+"/") {
@@ -392,7 +400,7 @@
 
 		err := walkFn(i, c.files[i])
 		if err == errSkipDir {
-			logf("android find in skip dir: %s", c.files[i].path)
+			glog.V(1).Infof("android find in skip dir: %s", c.files[i].path)
 			skipdirs = append(skipdirs, c.files[i].path)
 			continue
 		}
@@ -408,7 +416,7 @@
 // if [ -d $1 ] ; then cd $1 ; find ./ -not -name '.*' -and -type f -and -not -type l ; fi
 func (c *androidFindCacheT) findInDir(w evalWriter, dir string) {
 	dir = filepath.Clean(dir)
-	logf("android find in dir cache: %s", dir)
+	glog.V(1).Infof("android find in dir cache: %s", dir)
 	c.walk(dir, func(_ int, fi fileInfo) error {
 		// -not -name '.*'
 		if strings.HasPrefix(filepath.Base(fi.path), ".") {
@@ -422,7 +430,7 @@
 		name := strings.TrimPrefix(fi.path, dir+"/")
 		name = "./" + name
 		w.writeWordString(name)
-		logf("android find in dir cache: %s=> %s", dir, name)
+		glog.V(1).Infof("android find in dir cache: %s=> %s", dir, name)
 		return nil
 	})
 }
@@ -434,12 +442,12 @@
 func (c *androidFindCacheT) findExtFilesUnder(w evalWriter, chdir, root, ext string) bool {
 	chdir = filepath.Clean(chdir)
 	dir := filepath.Join(chdir, root)
-	logf("android find %s in dir cache: %s %s", ext, chdir, root)
+	glog.V(1).Infof("android find %s in dir cache: %s %s", ext, chdir, root)
 	// check symlinks
 	var matches []int
 	err := c.walk(dir, func(i int, fi fileInfo) error {
 		if fi.mode&os.ModeSymlink == os.ModeSymlink {
-			logf("android find %s in dir cache: detect symlink %s %v", ext, c.files[i].path, c.files[i].mode)
+			glog.Warningf("android find %s in dir cache: detect symlink %s %v", ext, c.files[i].path, c.files[i].mode)
 			return fmt.Errorf("symlink %s", fi.path)
 		}
 		matches = append(matches, i)
@@ -462,7 +470,7 @@
 		}
 		name := strings.TrimPrefix(fi.path, chdir+"/")
 		w.writeWordString(name)
-		logf("android find %s in dir cache: %s=> %s", ext, dir, name)
+		glog.V(1).Infof("android find %s in dir cache: %s=> %s", ext, dir, name)
 	}
 	return true
 }
@@ -474,7 +482,7 @@
 // -name "overview.html" -a \! -name ".*.swp" -a \! -name ".DS_Store" \
 // -a \! -name "*~" -print )
 func (c *androidFindCacheT) findJavaResourceFileGroup(w evalWriter, dir string) {
-	logf("android find java resource in dir cache: %s", dir)
+	glog.V(1).Infof("android find java resource in dir cache: %s", dir)
 	c.walk(filepath.Clean(dir), func(_ int, fi fileInfo) error {
 		// -type d -a -name ".svn" -prune
 		if fi.mode.IsDir() && filepath.Base(fi.path) == ".svn" {
@@ -499,7 +507,7 @@
 		name := strings.TrimPrefix(fi.path, dir+"/")
 		name = "./" + name
 		w.writeWordString(name)
-		logf("android find java resource in dir cache: %s=> %s", dir, name)
+		glog.V(1).Infof("android find java resource in dir cache: %s=> %s", dir, name)
 		return nil
 	})
 }
@@ -517,7 +525,7 @@
 			dir = ""
 		}
 		depth := strings.Count(dir, "/")
-		// logf("android findleaves dir=%q depth=%d dirs=%q", dir, depth, dirs)
+		// glog.V(1).Infof("android findleaves dir=%q depth=%d dirs=%q", dir, depth, dirs)
 		i := sort.Search(len(c.leaves), func(i int) bool {
 			di := strings.Count(c.leaves[i].path, "/")
 			if di != depth {
@@ -529,7 +537,7 @@
 			}
 			return c.leaves[i].path >= dir
 		})
-		logf("android findleaves dir=%q i=%d/%d", dir, i, len(c.leaves))
+		glog.V(1).Infof("android findleaves dir=%q i=%d/%d", dir, i, len(c.leaves))
 
 	Scandir:
 		for ; i < len(c.leaves); i++ {
@@ -543,7 +551,7 @@
 				if !c.leaves[i].mode.IsDir() && filepath.Base(c.leaves[i].path) == name {
 					n := "./" + c.leaves[i].path
 					found = append(found, n)
-					logf("android findleaves name=%s=> %s (depth=%d topdepth=%d mindepth=%d)", name, n, depth, topdepth, mindepth)
+					glog.V(1).Infof("android findleaves name=%s=> %s (depth=%d topdepth=%d mindepth=%d)", name, n, depth, topdepth, mindepth)
 					break Scandir
 				}
 			}
@@ -551,9 +559,9 @@
 				dirs = append(dirs, c.leaves[i].path)
 			}
 		}
-		// logf("android findleaves next dirs=%q", dirs)
+		// glog.V(1).Infof("android findleaves next dirs=%q", dirs)
 	}
-	logf("android findleave done")
+	glog.V(1).Infof("android findleave done")
 	sort.Strings(found)
 	for _, f := range found {
 		w.writeWordString(f)
diff --git a/runtest.rb b/runtest.rb
index 45f42e9..16a8cb9 100755
--- a/runtest.rb
+++ b/runtest.rb
@@ -30,6 +30,7 @@
   files.delete('Makefile')
   files.delete('gmon.out')
   files.reject!{|f|f =~ /\.json$/}
+  files.reject!{|f|f =~ /^kati\.*/}
   files
 end
 
@@ -152,7 +153,7 @@
     cleanup
     testcases.each do |tc|
       json = "#{tc.empty? ? 'test' : tc}"
-      cmd = "../../kati -save_json=#{json}.json -kati_log #{tc} 2>&1"
+      cmd = "../../kati -save_json=#{json}.json -log_dir=. #{tc} 2>&1"
       if ckati
         cmd = "../../ckati --use_find_emulator #{tc} 2>&1"
       end
@@ -193,7 +194,7 @@
     if name !~ /^err_/ && test_serialization && !expected_failure
       testcases.each do |tc|
         json = "#{tc.empty? ? 'test' : tc}"
-        cmd = "../../kati -save_json=#{json}_2.json -load_json=#{json}.json -n -kati_log #{tc} 2>&1"
+        cmd = "../../kati -save_json=#{json}_2.json -load_json=#{json}.json -n -log_dir=. #{tc} 2>&1"
         res = IO.popen(cmd, 'r:binary', &:read)
         if !File.exist?("#{json}.json") || !File.exist?("#{json}_2.json")
           puts "#{name}##{json}: Serialize failure (not exist)"
@@ -217,7 +218,7 @@
     cmd = "sh ../../#{sh} make"
     expected = IO.popen(cmd, 'r:binary', &:read)
     cleanup
-    cmd = "sh ../../#{sh} ../../kati --use_cache --kati_log"
+    cmd = "sh ../../#{sh} ../../kati --use_cache -log_dir=."
     output = IO.popen(cmd, 'r:binary', &:read)
 
     expected = normalize_make_log(expected)
diff --git a/serialize.go b/serialize.go
index 33ca981..3d49d2f 100644
--- a/serialize.go
+++ b/serialize.go
@@ -28,6 +28,8 @@
 	"sort"
 	"strconv"
 	"time"
+
+	"github.com/golang/glog"
 )
 
 const (
@@ -686,7 +688,7 @@
 }
 
 func deserializeGraph(g serializableGraph) (*DepGraph, error) {
-	if LogFlag || StatsFlag {
+	if StatsFlag {
 		showSerializedGraphStats(g)
 	}
 	nodes, err := deserializeNodes(g)
@@ -757,13 +759,13 @@
 
 	filename := cacheFilename(makefile, roots)
 	if !exists(filename) {
-		logAlways("Cache not found")
+		glog.Warningf("Cache not found %q", filename)
 		return nil, fmt.Errorf("cache not found: %s", filename)
 	}
 
 	g, err := GOB.Load(filename)
 	if err != nil {
-		logAlways("Cache load error: %v", err)
+		glog.Warning("Cache load error %q: %v", filename, err)
 		return nil, err
 	}
 	for _, mk := range g.accessedMks {
@@ -772,22 +774,22 @@
 		}
 		if mk.State == fileNotExists {
 			if exists(mk.Filename) {
-				logAlways("Cache expired: %s", mk.Filename)
+				glog.Infof("Cache expired: %s", mk.Filename)
 				return nil, fmt.Errorf("cache expired: %s", mk.Filename)
 			}
 		} else {
 			c, err := ioutil.ReadFile(mk.Filename)
 			if err != nil {
-				logAlways("Cache expired: %s", mk.Filename)
+				glog.Infof("Cache expired: %s", mk.Filename)
 				return nil, fmt.Errorf("cache expired: %s", mk.Filename)
 			}
 			h := sha1.Sum(c)
 			if !bytes.Equal(h[:], mk.Hash[:]) {
-				logAlways("Cache expired: %s", mk.Filename)
+				glog.Infof("Cache expired: %s", mk.Filename)
 				return nil, fmt.Errorf("cache expired: %s", mk.Filename)
 			}
 		}
 	}
-	logAlways("Cache found!")
+	glog.Info("Cache found in %q", filename)
 	return g, nil
 }
diff --git a/shellutil.go b/shellutil.go
index 2275a93..6aa6b90 100644
--- a/shellutil.go
+++ b/shellutil.go
@@ -18,6 +18,8 @@
 	"fmt"
 	"strings"
 	"time"
+
+	"github.com/golang/glog"
 )
 
 var shBuiltins = []struct {
@@ -280,13 +282,13 @@
 	}
 	dir := string(trimSpaceBytes(fargs[0]))
 	abuf.release()
-	logf("shellAndroidFindFileInDir %s => %s", f.dir.String(), dir)
+	glog.V(1).Infof("shellAndroidFindFileInDir %s => %s", f.dir.String(), dir)
 	if strings.Contains(dir, "..") {
-		logf("shellAndroidFindFileInDir contains ..: call original shell")
+		glog.Warningf("shellAndroidFindFileInDir contains ..: call original shell")
 		return f.funcShell.Eval(w, ev)
 	}
 	if !androidFindCache.ready() {
-		logf("shellAndroidFindFileInDir androidFindCache is not ready: call original shell")
+		glog.Warningf("shellAndroidFindFileInDir androidFindCache is not ready: call original shell")
 		return f.funcShell.Eval(w, ev)
 	}
 	androidFindCache.findInDir(w, dir)
@@ -323,20 +325,20 @@
 		roots = append(roots, root)
 	}
 	wb.release()
-	logf("shellAndroidFindExtFilesUnder %s,%s => %s,%s", f.chdir.String(), f.roots.String(), chdir, roots)
+	glog.V(1).Infof("shellAndroidFindExtFilesUnder %s,%s => %s,%s", f.chdir.String(), f.roots.String(), chdir, roots)
 	if strings.Contains(chdir, "..") || hasDotDot {
-		logf("shellAndroidFindExtFilesUnder contains ..: call original shell")
+		glog.Warningf("shellAndroidFindExtFilesUnder contains ..: call original shell")
 		return f.funcShell.Eval(w, ev)
 	}
 	if !androidFindCache.ready() {
-		logf("shellAndroidFindExtFilesUnder androidFindCache is not ready: call original shell")
+		glog.Warningf("shellAndroidFindExtFilesUnder androidFindCache is not ready: call original shell")
 		return f.funcShell.Eval(w, ev)
 	}
 	buf := newEbuf()
 	for _, root := range roots {
 		if !androidFindCache.findExtFilesUnder(buf, chdir, root, f.ext) {
 			buf.release()
-			logf("shellAndroidFindExtFilesUnder androidFindCache couldn't handle: call original shell")
+			glog.Warningf("shellAndroidFindExtFilesUnder androidFindCache couldn't handle: call original shell")
 			return f.funcShell.Eval(w, ev)
 		}
 	}
@@ -358,13 +360,13 @@
 	}
 	dir := string(trimSpaceBytes(fargs[0]))
 	abuf.release()
-	logf("shellAndroidFindJavaResourceFileGroup %s => %s", f.dir.String(), dir)
+	glog.V(1).Infof("shellAndroidFindJavaResourceFileGroup %s => %s", f.dir.String(), dir)
 	if strings.Contains(dir, "..") {
-		logf("shellAndroidFindJavaResourceFileGroup contains ..: call original shell")
+		glog.Warningf("shellAndroidFindJavaResourceFileGroup contains ..: call original shell")
 		return f.funcShell.Eval(w, ev)
 	}
 	if !androidFindCache.ready() {
-		logf("shellAndroidFindJavaResourceFileGroup androidFindCache is not ready: call original shell")
+		glog.Warningf("shellAndroidFindJavaResourceFileGroup androidFindCache is not ready: call original shell")
 		return f.funcShell.Eval(w, ev)
 	}
 	androidFindCache.findJavaResourceFileGroup(w, dir)
@@ -381,7 +383,7 @@
 
 func (f *funcShellAndroidFindleaves) Eval(w evalWriter, ev *Evaluator) error {
 	if !androidFindCache.leavesReady() {
-		logf("shellAndroidFindleaves androidFindCache is not ready: call original shell")
+		glog.Warningf("shellAndroidFindleaves androidFindCache is not ready: call original shell")
 		return f.funcShell.Eval(w, ev)
 	}
 	abuf := newEbuf()
@@ -408,7 +410,7 @@
 	for _, word := range wb.words {
 		dir := string(word)
 		if strings.Contains(dir, "..") {
-			logf("shellAndroidFindleaves contains .. in %s: call original shell", dir)
+			glog.Warningf("shellAndroidFindleaves contains .. in %s: call original shell", dir)
 			return f.funcShell.Eval(w, ev)
 		}
 		dirs = append(dirs, dir)
diff --git a/strutil.go b/strutil.go
index e64588a..e8396c4 100644
--- a/strutil.go
+++ b/strutil.go
@@ -18,6 +18,8 @@
 	"bytes"
 	"path/filepath"
 	"strings"
+
+	"github.com/golang/glog"
 )
 
 var wsbytes = [256]bool{' ': true, '\t': true, '\n': true, '\r': true}
@@ -48,7 +50,7 @@
 	if tokStart >= 0 {
 		r = append(r, s[tokStart:])
 	}
-	logf("splitSpace(%q)=%q", s, r)
+	glog.V(2).Infof("splitSpace(%q)=%q", s, r)
 	return r
 }
 
@@ -69,7 +71,7 @@
 	if tokStart >= 0 {
 		r = append(r, s[tokStart:])
 	}
-	logf("splitSpace(%q)=%q", s, r)
+	glog.V(2).Infof("splitSpace(%q)=%q", s, r)
 	return r
 }
 
diff --git a/testcase/kati_cache.sh b/testcase/kati_cache.sh
index 8cb51a2..a8a6919 100755
--- a/testcase/kati_cache.sh
+++ b/testcase/kati_cache.sh
@@ -27,18 +27,18 @@
 # Pretend to be a very old Makefile.
 touch -t 197101010000 Makefile
 
-"$@" | tee /tmp/log 2>&1
+"$@"
 
 if [ -e .kati_cache.Makefile ]; then
-  if ! grep '\*kati\*: Cache not found' /tmp/log; then
+  if ! grep -q 'Cache not found' kati.INFO; then
     echo 'Cache unexpectedly found'
   fi
 fi
 
-"$@" | tee /tmp/log 2>&1
+"$@"
 
 if [ -e .kati_cache.Makefile ]; then
-  if ! grep '\*kati\*: Cache found' /tmp/log; then
+  if ! grep -q 'Cache found' kati.INFO; then
     echo 'Cache unexpectedly not found'
   fi
 fi
@@ -47,10 +47,10 @@
 	echo bar
 EOF
 
-"$@" | tee /tmp/log 2>&1
+"$@"
 
 if [ -e .kati_cache.Makefile ]; then
-  if ! grep '\*kati\*: Cache expired' /tmp/log; then
+  if ! grep -q 'Cache expired' kati.INFO; then
     echo 'Cache unexpectedly not expired'
   fi
 fi
diff --git a/worker.go b/worker.go
index 7d79665..0b1457d 100644
--- a/worker.go
+++ b/worker.go
@@ -22,6 +22,8 @@
 	"os/exec"
 	"syscall"
 	"time"
+
+	"github.com/golang/glog"
 )
 
 type job struct {
@@ -177,7 +179,7 @@
 			return nil
 		}
 		j := heap.Pop(&wm.readyQueue).(*job)
-		logf("run: %s", j.n.Output)
+		glog.V(1).Infof("run: %s", j.n.Output)
 
 		j.numDeps = -1 // Do not let other workers pick this.
 		w := wm.freeWorkers[0]
@@ -190,7 +192,7 @@
 func (wm *workerManager) updateParents(j *job) {
 	for _, p := range j.parents {
 		p.numDeps--
-		logf("child: %s (%d)", p.n.Output, p.numDeps)
+		glog.V(1).Infof("child: %s (%d)", p.n.Output, p.numDeps)
 		if p.depsTs < j.outputTs {
 			p.depsTs = j.outputTs
 		}
@@ -261,7 +263,7 @@
 		return
 	}
 	heap.Push(&wm.readyQueue, j)
-	logf("ready: %s", j.n.Output)
+	glog.V(1).Infof("ready: %s", j.n.Output)
 }
 
 func (wm *workerManager) handleNewDep(j *job, neededBy *job) {
@@ -282,12 +284,12 @@
 	for wm.hasTodo() || len(wm.busyWorkers) > 0 || len(wm.runnings) > 0 || !done {
 		select {
 		case j := <-wm.jobChan:
-			logf("wait: %s (%d)", j.n.Output, j.numDeps)
+			glog.V(1).Infof("wait: %s (%d)", j.n.Output, j.numDeps)
 			j.id = len(wm.jobs) + 1
 			wm.jobs = append(wm.jobs, j)
 			wm.maybePushToReadyQueue(j)
 		case jr := <-wm.resultChan:
-			logf("done: %s", jr.j.n.Output)
+			glog.V(1).Infof("done: %s", jr.j.n.Output)
 			delete(wm.busyWorkers, jr.w)
 			wm.freeWorkers = append(wm.freeWorkers, jr.w)
 			wm.updateParents(jr.j)
@@ -299,7 +301,7 @@
 			}
 		case af := <-wm.newDepChan:
 			wm.handleNewDep(af.j, af.neededBy)
-			logf("dep: %s (%d) %s", af.neededBy.n.Output, af.neededBy.numDeps, af.j.n.Output)
+			glog.V(1).Infof("dep: %s (%d) %s", af.neededBy.n.Output, af.neededBy.numDeps, af.j.n.Output)
 		case done = <-wm.waitChan:
 		}
 		err = wm.handleJobs()
@@ -307,7 +309,7 @@
 			break Loop
 		}
 
-		logf("job=%d ready=%d free=%d busy=%d", len(wm.jobs)-wm.finishCnt, wm.readyQueue.Len(), len(wm.freeWorkers), len(wm.busyWorkers))
+		glog.V(1).Infof("job=%d ready=%d free=%d busy=%d", len(wm.jobs)-wm.finishCnt, wm.readyQueue.Len(), len(wm.freeWorkers), len(wm.busyWorkers))
 	}
 	if !done {
 		<-wm.waitChan