<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"mime/multipart"
	"os"
	"strings"

	"github.com/rogpeppe/rog-go/reverse"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
)

const (
	defaultLines = 1e5
	maxLines     = 1e6
)

func NewCmdLogSend(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	return cli.Command{
		Name:  "send",
		Usage: "Send recent debug logs to keybase",
		Action: func(c *cli.Context) {
			cl.ChooseCommand(&CmdLogSend{Contextified: libkb.NewContextified(g)}, "send", c)
		},
		Flags: []cli.Flag{
			cli.IntFlag{
				Name:  "n",
				Usage: "Number of lines in each log file",
			},
		},
	}
}

type CmdLogSend struct {
	libkb.Contextified
	numLines int
}

func (c *CmdLogSend) Run() error {

	c.G().Log.Debug("getting keybase status")
	statusCmd := &CmdStatus{Contextified: libkb.NewContextified(c.G())}
	status, err := statusCmd.load()
	if err != nil {
		return err
	}
	statusJSON, err := json.Marshal(status)
	if err != nil {
		return err
	}
	c.G().Log.Debug("status json: %s", statusJSON)

	c.G().Log.Debug("tailing kbfs log %q", status.KBFS.Log)
	kbfsLog := c.tail(status.KBFS.Log, c.numLines)

	c.G().Log.Debug("tailing service log %q", status.Service.Log)
	svcLog := c.tail(status.Service.Log, c.numLines)

	c.G().Log.Debug("tailing desktop log %q", status.Desktop.Log)
	desktopLog := c.tail(status.Desktop.Log, c.numLines)

	return c.post(string(statusJSON), kbfsLog, svcLog, desktopLog)
}

func (c *CmdLogSend) post(status, kbfsLog, svcLog, desktopLog string) error {
	c.G().Log.Debug("sending status + logs to keybase")

	var body bytes.Buffer
	mpart := multipart.NewWriter(&body)

	if err := addFile(mpart, "status_gz", "status.gz", status); err != nil {
		return err
	}
	if err := addFile(mpart, "kbfs_log_gz", "kbfs_log.gz", kbfsLog); err != nil {
		return err
	}
	if err := addFile(mpart, "keybase_log_gz", "keybase_log.gz", svcLog); err != nil {
		return err
	}
	if err := addFile(mpart, "gui_log_gz", "gui_log.gz", desktopLog); err != nil {
		return err
	}

	if err := mpart.Close(); err != nil {
		return err
	}

	c.G().Log.Debug("body size: %d\n", body.Len())

	arg := libkb.APIArg{
		Contextified: libkb.NewContextified(c.G()),
		Endpoint:     "logdump/send",
	}

	resp, err := c.G().API.PostRaw(arg, mpart.FormDataContentType(), &body)
	if err != nil {
		c.G().Log.Debug("post error: %s", err)
		return err
	}

	id, err := resp.Body.AtKey("logdump_id").GetString()
	if err != nil {
		return err
	}

	c.G().Log.Info("logs sent, dump id = %q", id)
	return nil
}

func addFile(mpart *multipart.Writer, param, filename, data string) error {
	if len(data) == 0 {
		return nil
	}

	part, err := mpart.CreateFormFile(param, filename)
	if err != nil {
		return err
	}
	gz := gzip.NewWriter(part)
	if _, err := gz.Write([]byte(data)); err != nil {
		return err
	}
	if err := gz.Close(); err != nil {
		return err
	}

	return nil
}

func (c *CmdLogSend) tail(filename string, numLines int) string {
	f, err := os.Open(filename)
	if err != nil {<commit_msg>Add confirmation, github url<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"mime/multipart"
	"os"
	"strings"

	"github.com/rogpeppe/rog-go/reverse"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
)

const (
	defaultLines = 1e5
	maxLines     = 1e6
)

func NewCmdLogSend(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	return cli.Command{
		Name:  "send",
		Usage: "Send recent debug logs to keybase",
		Action: func(c *cli.Context) {
			cl.ChooseCommand(&CmdLogSend{Contextified: libkb.NewContextified(g)}, "send", c)
		},
		Flags: []cli.Flag{
			cli.IntFlag{
				Name:  "n",
				Usage: "Number of lines in each log file",
			},
		},
	}
}

type CmdLogSend struct {
	libkb.Contextified
	numLines int
}

func (c *CmdLogSend) Run() error {
	if err := c.confirm(); err != nil {
		return err
	}

	c.G().Log.Debug("getting keybase status")
	statusCmd := &CmdStatus{Contextified: libkb.NewContextified(c.G())}
	status, err := statusCmd.load()
	if err != nil {
		return err
	}
	statusJSON, err := json.Marshal(status)
	if err != nil {
		return err
	}

	c.G().Log.Debug("tailing kbfs log %q", status.KBFS.Log)
	kbfsLog := c.tail(status.KBFS.Log, c.numLines)

	c.G().Log.Debug("tailing service log %q", status.Service.Log)
	svcLog := c.tail(status.Service.Log, c.numLines)

	c.G().Log.Debug("tailing desktop log %q", status.Desktop.Log)
	desktopLog := c.tail(status.Desktop.Log, c.numLines)

	return c.post(string(statusJSON), kbfsLog, svcLog, desktopLog)
}

func (c *CmdLogSend) confirm() error {
	ui := c.G().UI.GetTerminalUI()
	ui.Printf("This command will send recent keybase log entries to keybase.io\n")
	ui.Printf("for debugging purposes only.\n\n")
	ui.Printf("While we have made every attempt to keep sensitive information\n")
	ui.Printf("out of the logs, we wanted to make sure you are ok with sending\n")
	ui.Printf("your log files.\n\n")
	return ui.PromptForConfirmation("Continue sending logs to keybase.io?")
}

func (c *CmdLogSend) post(status, kbfsLog, svcLog, desktopLog string) error {
	c.G().Log.Debug("sending status + logs to keybase")

	var body bytes.Buffer
	mpart := multipart.NewWriter(&body)

	if err := addFile(mpart, "status_gz", "status.gz", status); err != nil {
		return err
	}
	if err := addFile(mpart, "kbfs_log_gz", "kbfs_log.gz", kbfsLog); err != nil {
		return err
	}
	if err := addFile(mpart, "keybase_log_gz", "keybase_log.gz", svcLog); err != nil {
		return err
	}
	if err := addFile(mpart, "gui_log_gz", "gui_log.gz", desktopLog); err != nil {
		return err
	}

	if err := mpart.Close(); err != nil {
		return err
	}

	c.G().Log.Debug("body size: %d\n", body.Len())

	arg := libkb.APIArg{
		Contextified: libkb.NewContextified(c.G()),
		Endpoint:     "logdump/send",
	}

	resp, err := c.G().API.PostRaw(arg, mpart.FormDataContentType(), &body)
	if err != nil {
		c.G().Log.Debug("post error: %s", err)
		return err
	}

	id, err := resp.Body.AtKey("logdump_id").GetString()
	if err != nil {
		return err
	}

	c.outputInstructions(id)
	return nil
}

func (c *CmdLogSend) outputInstructions(id string) {
	ui := c.G().UI.GetTerminalUI()
	ui.Printf("keybase.io received your logs successfully! Your log dump ID is:\n\n")
	ui.Printf("\t%s\n\n", id)
	ui.Printf("Here's a URL to submit a bug report containing this ID:\n\n")
	ui.Printf("\thttps://github.com/keybase/keybase-issues/issues/new?body=log+dump+id+%s\n\n", id)
	ui.Printf("Thanks!\n")
}

func addFile(mpart *multipart.Writer, param, filename, data string) error {
	if len(data) == 0 {
		return nil
	}

	part, err := mpart.CreateFormFile(param, filename)
	if err != nil {
		return err
	}
	gz := gzip.NewWriter(part)
	if _, err := gz.Write([]byte(data)); err != nil {
		return err
	}
	if err := gz.Close(); err != nil {
		return err
	}

	return nil
}

func (c *CmdLogSend) tail(filename string, numLines int) string {
	f, err := os.Open(filename)
	if err != nil {
{'fraction_non_alphanumeric': 0.16208828754954216, 'fraction_numerical': 0.0024600246002460025, 'mean_word_length': 3.8786666666666667, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7454557', 'n_tokens_mistral': 2919, 'n_tokens_neox': 2794, 'n_words': 692}