github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/jujud/dumplogs/dumplogs.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // A simple command for dumping out the logs stored in 5 // MongoDB. Intended to be use in emergency situations to recover logs 6 // when Juju is broken somehow. 7 8 package dumplogs 9 10 import ( 11 "bufio" 12 "fmt" 13 "io/ioutil" 14 "os" 15 "path/filepath" 16 "time" 17 18 "github.com/juju/clock" 19 "github.com/juju/cmd" 20 "github.com/juju/errors" 21 "github.com/juju/gnuflag" 22 "github.com/juju/loggo" 23 "gopkg.in/juju/names.v2" 24 25 "github.com/juju/juju/agent" 26 jujucmd "github.com/juju/juju/cmd" 27 jujudagent "github.com/juju/juju/cmd/jujud/agent" 28 corenames "github.com/juju/juju/juju/names" 29 "github.com/juju/juju/mongo" 30 "github.com/juju/juju/state" 31 ) 32 33 // NewCommand returns a new Command instance which implements the 34 // "juju-dumplogs" command. 35 func NewCommand() cmd.Command { 36 return &dumpLogsCommand{ 37 agentConfig: jujudagent.NewAgentConf(""), 38 } 39 } 40 41 type dumpLogsCommand struct { 42 cmd.CommandBase 43 agentConfig jujudagent.AgentConf 44 machineId string 45 outDir string 46 } 47 48 // Info implements cmd.Command. 49 func (c *dumpLogsCommand) Info() *cmd.Info { 50 doc := ` 51 This tool can be used to access Juju's logs when the Juju controller 52 isn't functioning for some reason. It must be run on a Juju controller 53 server, connecting to the Juju database instance and generating a log 54 file for each model that exists in the controller. 55 56 Log files are written out to the current working directory by 57 default. Use -d / --output-directory option to specify an alternate 58 target directory. 59 60 In order to connect to the database, the local machine agent's 61 configuration is needed. In most circumstances the configuration will 62 be found automatically. The --data-dir and/or --machine-id options may 63 be required if the agent configuration can't be found automatically. 64 `[1:] 65 return jujucmd.Info(&cmd.Info{ 66 Name: corenames.JujuDumpLogs, 67 Purpose: "output the logs that are stored in the local Juju database", 68 Doc: doc, 69 }) 70 } 71 72 // SetFlags implements cmd.Command. 73 func (c *dumpLogsCommand) SetFlags(f *gnuflag.FlagSet) { 74 c.agentConfig.AddFlags(f) 75 f.StringVar(&c.outDir, "d", ".", "directory to write logs files to") 76 f.StringVar(&c.outDir, "output-directory", ".", "") 77 f.StringVar(&c.machineId, "machine-id", "", "id of the machine on this host (optional)") 78 } 79 80 // Init implements cmd.Command. 81 func (c *dumpLogsCommand) Init(args []string) error { 82 err := c.agentConfig.CheckArgs(args) 83 if err != nil { 84 return errors.Trace(err) 85 } 86 87 if c.machineId == "" { 88 machineId, err := c.findMachineId(c.agentConfig.DataDir()) 89 if err != nil { 90 return errors.Trace(err) 91 } 92 c.machineId = machineId 93 } else if !names.IsValidMachine(c.machineId) { 94 return errors.New("--machine-id option expects a non-negative integer") 95 } 96 97 err = c.agentConfig.ReadConfig(names.NewMachineTag(c.machineId).String()) 98 if err != nil { 99 return errors.Trace(err) 100 } 101 102 return nil 103 } 104 105 // Run implements cmd.Command. 106 func (c *dumpLogsCommand) Run(ctx *cmd.Context) error { 107 config := c.agentConfig.CurrentConfig() 108 info, ok := config.MongoInfo() 109 if !ok { 110 return errors.New("no database connection info available (is this a controller host?)") 111 } 112 113 session, err := mongo.DialWithInfo(*info, mongo.DefaultDialOpts()) 114 if err != nil { 115 return errors.Trace(err) 116 } 117 defer session.Close() 118 119 statePool, err := state.OpenStatePool(state.OpenParams{ 120 Clock: clock.WallClock, 121 ControllerTag: config.Controller(), 122 ControllerModelTag: config.Model(), 123 MongoSession: session, 124 }) 125 if err != nil { 126 return errors.Annotate(err, "failed to connect to database") 127 } 128 defer statePool.Close() 129 st0 := statePool.SystemState() 130 modelUUIDs, err := st0.AllModelUUIDs() 131 if err != nil { 132 return errors.Annotate(err, "failed to look up models") 133 } 134 for _, modelUUID := range modelUUIDs { 135 err := c.dumpLogsForEnv(ctx, statePool, names.NewModelTag(modelUUID)) 136 if err != nil { 137 return errors.Annotatef(err, "failed to dump logs for model %s", modelUUID) 138 } 139 } 140 141 return nil 142 } 143 144 func (c *dumpLogsCommand) findMachineId(dataDir string) (string, error) { 145 entries, err := ioutil.ReadDir(agent.BaseDir(dataDir)) 146 if err != nil { 147 return "", errors.Annotate(err, "failed to read agent configuration base directory") 148 } 149 for _, entry := range entries { 150 if entry.IsDir() { 151 tag, err := names.ParseMachineTag(entry.Name()) 152 if err == nil { 153 return tag.Id(), nil 154 } 155 } 156 } 157 return "", errors.New("no machine agent configuration found") 158 } 159 160 func (c *dumpLogsCommand) dumpLogsForEnv(ctx *cmd.Context, statePool *state.StatePool, tag names.ModelTag) error { 161 st, err := statePool.Get(tag.Id()) 162 if err != nil { 163 if errors.IsNotFound(err) { 164 ctx.Infof("model with uuid %v has been removed", tag.Id()) 165 return nil 166 } 167 return errors.Annotate(err, "failed open model") 168 } 169 defer st.Release() 170 171 logName := ctx.AbsPath(filepath.Join(c.outDir, fmt.Sprintf("%s.log", tag.Id()))) 172 ctx.Infof("writing to %s", logName) 173 174 file, err := os.Create(logName) 175 if err != nil { 176 return errors.Annotate(err, "failed to open output file") 177 } 178 defer file.Close() 179 180 writer := bufio.NewWriter(file) 181 defer writer.Flush() 182 183 tailer, err := state.NewLogTailer(st, state.LogTailerParams{NoTail: true}) 184 if err != nil { 185 return errors.Annotate(err, "failed to create a log tailer") 186 } 187 logs := tailer.Logs() 188 for { 189 rec, ok := <-logs 190 if !ok { 191 break 192 } 193 writer.WriteString(c.format( 194 rec.Time, 195 rec.Level, 196 rec.Entity.String(), 197 rec.Module, 198 rec.Message, 199 ) + "\n") 200 } 201 202 return nil 203 } 204 205 func (c *dumpLogsCommand) format(timestamp time.Time, level loggo.Level, entity, module, message string) string { 206 ts := timestamp.In(time.UTC).Format("2006-01-02 15:04:05") 207 return fmt.Sprintf("%s: %s %s %s %s", entity, ts, level, module, message) 208 }