嵌入式linux下的c语言日志log模块,功能增强(二)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 嵌入式linux下的c语言日志log模块,功能增强(二)

日志是应用的镜子,可以发现应用中的问题,重要性不言而喻。


打造一智能日志模块,让运维朝着自动化方向大步迈进。提高效率,降低成本,这也是一种创造利润的途径。


如今网络越来越方便,这个日志组件除了常规的记录日志外,可以对日志分等级显示不同的颜色,支持按日期切割,支持控制文件大小及定时清理若干天的记录文件。更重要的是,一种远程诊断的方案。公司开辟一公共外网ftp服务器,这不费不少钱吧,也放心这服务器占不满空间。现场遇到问题了,只要网络不是问题,就都不是问题,这日志组件可由运维人员去触发机器一下,或让客户手工摁一下,机器主动ftp上去一个日志文件。或者应用里增加自主触发机制,当应用执行到FATAL,严重错误不该出现的地方时,或应用异常崩溃时,主动触发上报日志文件FTP到后台服务器。后台服务器增加监视,发现是致命bug日志,主动推送邮件到开发或运维人员邮箱。开发或运维人员总是盯数据肯定不感兴趣,往往不能及时发现问题,那么这种让问题主动去找你的思路,让你主动的去关注问题,而不是等到问题暴露了才去解决。


这样运维人员还用大热天到处跑吗?还用动不动就抓包?出差?都不用的。只要网络ok,数据跑路的都能实现不用人参与。


那么一个日志模块,什么是你想要的功能?


对我而言,这几点是必须的。


1·.日志分等级记录,可控制等级。


2.不同等级日志显示不同颜色。


3.增加是否启用日志输出到文件开关,可以选择把日志保存到文件中.


4.写文件属于耗时操作,这块要考虑异步写日志,不能阻塞应用或影响应用运行时间效率。


5.按日期生成日志文件,可配置保留多少天,超过设定的天数则自动清除超过天数的日志。


6.可增加参数设定限制日志文件的大小,超过限制大小可选择是从头覆盖还是删除重记。

以下为增强功能,


7.发现应用异常时,不但主动记录日志,而且主动上报异常日志文件。可通过FTP服务自动上报日志到后台FTP服务器,


把问题的发现,变被动为主动。当应用出现不该出现的问题时,主动上报。助力自动化运维。


即只要网络是OK的,不再让用户跑路。不再被动的发现问题。让问题解决于被暴露之前,提高产品的口碑与竞争力。


8.可以由运维人员去简单的触发一下,就把终端的日志通过网络传上去,不用再去找线,不用再去找U盘,不用再去想法把日志拷贝出来再带回电脑上发给开发人员。


9.可以由后台参数控制或比如发送短信,自动控制让某一台终端上报日志。


10.支持日志压缩,压缩为zip或7z等文件,缩小体积,便于储存和通过网络传输。


日志的意义在于排查问题和运维。


若所有机器都把日志上传上去则失去了意义,且服务器也顶不住。而这种被动的触发,针对某个机器的上送是可行的且有价值的。并且还可以在应用中增加当应用出现客户没发现缺不该出现的问题时,主动触发上报异常日志。这为提高产品的稳定性,杜绝问题造成的严重性而未发现提供先机。当某天发现一机器偶然吐出一异常的bug日志时,且这日志暴露的问题若不解决将造成严重后果,而你恰好在你的邮箱里看到,这就在不知不觉中主动发现了问题。不用运维人员去找你,客户去找你,机器向你求救了。那么,救救它吧。


OK,按着这个思想,以下是一个实现,c语言的log模块:


至于FTP部分,文件压缩为zip部分,用go来写,更容易。这也就是为啥用go来开发嵌入式很合适。要是让你用c写一个ftp,你试试?


这就体现了用go开发嵌入式linux的强大之处。用go,简短的几行代码就ok了。且在终端上跑的很溜。


/**
日志打印示例。
使用:
mylog(DEBUG, "This is debug info\n");
结果:
[2018-07-22 23:37:27:172] [DEBUG] [main.cpp:5] This is debug info
默认打印当前时间(精确到毫秒)、文件名称、行号。
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <errno.h>
#include <dirent.h>
#include <stdlib.h>
#include "log.h"
//#ifndef LOGLEVEL
//#define LOGLEVEL DEBUG
//#endif
// 使用了GNU C扩展语法,只在gcc(C语言)生效,
// g++的c++版本编译不通过
static const char* s_loginfo[] = {
    [ERROR] = "ERROR",
    [WARN]  = "WARN",
    [INFO]  = "INFO",
    [DEBUG] = "DEBUG",
};
static char file_names[LOGFILE_MAXCOUNT][LOGFILE_NAMELENTH];
//记录文件名前缀(最好取自终端编号)
static char file_prifix[LOGFILE_NAMELENTH];
//linux消息队列
static int s_msg_id;
static int r_msg_id;
#define MSG_TYPE 1001
#define MAX_TEXT 1024
struct msg_st{
  long int msg_type;
  char text[MAX_TEXT];
};
static pthread_t tid;
//=============================================
static void get_timestamp(char *buffer)
{
    time_t t;
    struct tm *p;
    struct timeval tv;
    int len;
    int millsec;
    t = time(NULL);
    p = localtime(&t);
    gettimeofday(&tv, NULL);
    millsec = (int)(tv.tv_usec / 1000);
    /* 时间格式:[2011-11-15 12:47:34:888] */
    len = snprintf(buffer, 32, "[%04d-%02d-%02d %02d:%02d:%02d:%03d] ",
        p->tm_year+1900, p->tm_mon+1,
        p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, millsec);
    buffer[len] = '\0';
}
//获取当前时间
static int get_curtime( char* outTimeStr )
{
  int ret = 0;
  time_t tTime;
  struct tm *tmTime;
  struct timeval mTime;
  time( &tTime );
  tmTime = localtime( &tTime );
  gettimeofday( &mTime, NULL );
  sprintf( outTimeStr, "%04d%02d%02d%02d%02d%02d",
      tmTime->tm_year + 1900, tmTime->tm_mon + 1,
      tmTime->tm_mday, tmTime->tm_hour,
      tmTime->tm_min, tmTime->tm_sec );
  return ret;
}
//创建文件夹
static int create_dir(const char *sPathName)  
{  
  char dirName[256];  
  strcpy(dirName, sPathName);  
  int i,len = strlen(dirName);
  for(i=1; i<len; i++)  
  {  
    if(dirName[i]=='/')  
    {  
      dirName[i] = 0; 
      if(access(dirName, 0)!=0)  
      {  
        if(mkdir(dirName, 0755)==-1)  
        {   
          fprintf(stderr,"mkdir   error\n");   
          return -1;   
        }  
      }  
      dirName[i] = '/';  
    }  
  }  
  return 0;  
} 
//获取文件大小
static unsigned long get_size(const char *path)
{
  unsigned long filesize = -1;  
  struct stat statbuff;
  if(stat(path, &statbuff) < 0){
    return filesize;
  }else{
    filesize = statbuff.st_size;
  }
  return filesize;
}
//文件是否存在,返回 1文件存在,返回0,文件不存在
static int file_exists(char *filename)
{
  return (access(filename, 0) == 0);
} 
static int read_filelist(char *basePath)
{
  DIR *dir;
  struct dirent *ptr;
  char base[1000];
  int count = 0;
  if ((dir=opendir(basePath)) == NULL)
  {
    fprintf(stderr,"Open dir error...");
    return -1;
  }
  while ((ptr=readdir(dir)) != NULL)
  {
    //printf("count=%d\n",i++);
    if(strcmp(ptr->d_name,".")==0 || strcmp(ptr->d_name,"..")==0)    ///current dir OR parrent dir
      continue;
    else if(ptr->d_type == 8)    ///file
    {
      printf("f_name:%s/%s\n",basePath,ptr->d_name);
      sprintf(file_names[count],"%s",ptr->d_name);
      count++;
      if(count > LOGFILE_MAXCOUNT -1){
        fprintf(stderr,"error!only allow files count=%d\n",LOGFILE_MAXCOUNT);
        return -2;
      }
    }
    else if(ptr->d_type == 10)    ///link file
      printf("l_name:%s/%s\n",basePath,ptr->d_name);
    else if(ptr->d_type == 4)    ///dir
    {
      printf("d_name:%s/%s\n",basePath,ptr->d_name);
      printf("this is a dir\n");
      continue;
      //是目录,继续递归读目录下面的
      //memset(base,'\0',sizeof(base));
      //strcpy(base,basePath);
      //strcat(base,"/");
      //strcat(base,ptr->d_name);
      //read_filelist(base);
    }
  }
  closedir(dir);
  return 0;
}
//处理日志文件是否保留
//原理算法:把日期转换成时间戳,然后由配置的允许保留的天数换算出一个时间范围,
//在遍历日志目录中所有的文件名,提取出日期,在这个时间范围内的保留,否则删除
//关键的地方,算出这个允许保留文件的时间范围,原理是日期转时间戳,再时间戳转日期
static int file_alives_proc()
{
  int ret = 0;
  char curtime[20];  //当前日期时间
  char deadtime[20]; //截止的历史日期
  printf("file_alives_proc:\n");
  get_curtime(curtime);
  //只取日期,省去时间
  memset(&curtime[8],0x30,6);
  printf("ailve maxdays:%d\n",LOGFILE_ALIVEDAYS);
  printf("curtime:%s\n",curtime);
  struct tm* tmp_time = (struct tm*)malloc(sizeof(struct tm));
  //字符串转时间
  strptime(curtime,"%Y%m%d%H%M%S",tmp_time);
  time_t t = mktime(tmp_time);
  printf("t now = %ld\n",t);
  free(tmp_time);  
  time_t t1 = t - LOGFILE_ALIVEDAYS*24*60*60;
  //再把t1转换为时间,即时间戳转时间
  struct tm *p; 
  p = gmtime(&t1); 
  //日期时间转字符串,由于只比较日期,因此忽略时间
  strftime(deadtime, sizeof(deadtime), "%Y%m%d000000", p);  
  printf("deadtime:%s\n",deadtime);
  //以上获取到了curtime和deadtime,有了这个时间范围,接下来就去找这个范围的日志
  //日志文件日期在这个范围内的保留,否则删除
  for(int i = 0; i < LOGFILE_MAXCOUNT; i++ )
  {
    if(strlen(file_names[i]) > 0)
    {
      printf("file_name=%s\n",file_names[i]);
      char ftime[20];
      memset(ftime,0,20);
      memset(ftime,0x30,8);
      //关键,这个截取不能错
      memcpy(ftime,&file_names[i][strlen(LOGFILE_PREFIX)+1+strlen(file_prifix)],8);
      printf("file_time=%s\n",ftime);
      //开始比较 是否在日期范围内
      if(memcmp(ftime,deadtime,8) > 0)
      {
        //大于截止日期的文件保留
        printf("%s------keep alive\n",file_names[i]);
      }else{
        printf("%s----------------dead,need remove!\n",file_names[i]);
        //删除文件
        char dfname[50];
        sprintf(dfname,"%s/%s",LOGFILE_PATH,file_names[i]);
        remove(dfname);
      }
      //
    }else{
      //printf("fname=NULL\n");
    }
  }
  return ret;
}
int open_msg(void) {
  key_t msgkey;
  if ((msgkey = ftok("/tmp", 'a')) < 0) {
    printf("send ftok failed!\n");
    return -1;
  }
  printf("----msgkey is %d\n",msgkey);
  if ((s_msg_id = msgget(msgkey, IPC_CREAT | 0666)) == -1) {
    printf("msgget failed!\n");
    return -1;
  }
  printf("----s_msg_id is %d\n",s_msg_id);
  msgctl(s_msg_id,IPC_RMID,0);//先删除,否则可能满,因其生命周期同内核
  if ((s_msg_id = msgget(msgkey, IPC_CREAT | 0666)) == -1) {
    printf("msgget failed!\n");
    return -1;
  }
  r_msg_id = s_msg_id;
  return 0;
}
static int send_msg(char *buf,int len){
  struct msg_st data;
  data.msg_type = MSG_TYPE;
  strcpy(data.text,buf);
  //s_msg_id = 0;
  if(msgsnd(s_msg_id,&data,len,IPC_NOWAIT) == -1){
    printf("msgsnd failed.\n");
    perror("msgsnd");
    return -1;
  }
  return 0;
}
//若大量连续发的太快,收的太慢,会导致发送失败
static int recv_msg(void)
{
  int rsize;
  struct msg_st data;
  int msgtype = MSG_TYPE;
  char tmpfname[ 50 ];
  char tmpTime[ 14 + 1 ];
  FILE* fp;
  unsigned long filesize;
  memset(data.text,0,sizeof(data.text));
  /*接收消息队列*/    
  //阻塞接收
  rsize = msgrcv(r_msg_id,&data,sizeof(data.text),msgtype,MSG_NOERROR );    
  if(rsize == -1)
  {
    if (errno != ENOMSG) {
      perror("msgrcv");
    }
    printf("No message available for msgrcv()\n");
    return -1;
  }
  else
  {
    //printf("message received: %s\n", data.text);
    get_curtime( tmpTime );
    sprintf( tmpfname, "%s/%s%s_%8.8s.log", LOGFILE_PATH, LOGFILE_PREFIX,file_prifix, tmpTime );
    fp = fopen( tmpfname, "a" );
    if ( NULL == fp )
    {
      fprintf(stderr,"failed to open file,filename=%s\n",tmpfname);
      return -2;
    }
    else
    {
      filesize = get_size(tmpfname);
    //  printf("filesize=%u\n",filesize);
      if((filesize/1024) > LOGFILE_MAXSIZE){
        fprintf(stderr,"failed to write log,only allow maxsize=%ukb,current size=%ukb\n",LOGFILE_MAXSIZE,filesize/1024);
        fclose(fp);
        return -3 ;
      }
      //printf("%s\n",tmpfname);
      fprintf( fp, "%s", data.text );
      fclose(fp);
    }
  }
  return 0;
}
void mylog1(const char* filename, int line, enum LogLevel level, const char* fmt, ...)
{
    if(level > LOGLEVEL)
        return;
    va_list arg_list;
    char buf[1024];
  char sbuf[MAX_TEXT];
    memset(buf, 0, 1024);
  memset(sbuf,0,MAX_TEXT);
    va_start(arg_list, fmt);
    vsnprintf(buf, 1024, fmt, arg_list);
    char time[32] = {0};
    // 去掉*可能*存在的目录路径,只保留文件名
    const char* tmp = strrchr(filename, '/');
    if (!tmp) tmp = filename;
    else tmp++;
    get_timestamp(time);
  switch(level){
    case DEBUG:
      //绿色
      sprintf(sbuf,"\033[1;32m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
      break;
    case INFO:
      //蓝色
      sprintf(sbuf,"\033[1;34m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
      break;
    case ERROR:
      //红色
      sprintf(sbuf,"\033[1;31m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
      break;
    case WARN:
      //黄色
      sprintf(sbuf,"\033[1;33m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
      break;
  }
  printf("%s",sbuf);
#if(LOGFILE_ENABLE)
  //记录日志到文件
  send_msg(sbuf,strlen(sbuf));
#endif
    va_end(arg_list);
}
//考虑了一点儿效率,操作IO比较耗时,那就异步写入吧
//想用单独的线程,负责写日志到文件,自己实现消息队列?
//不这么做了,直接使用linux的消息队列吧
static void* thread_writelog(void* args)
{
  mylog(INFO,"thread_writelog begin...\n");
  while(1)
  {
    recv_msg();
    usleep(20*1000);              
  }
}
//参数prifix,日志文件名前缀,传终端编号
int init_log(char *prifix)
{
  int ret = 0;
  printf("init log:\n");
#if(LOGFILE_ENABLE)
  //检查目录是否存在
  ret = create_dir(LOGFILE_PATH);
  if(0 != ret){
    return ret;
  }
  printf("create dir %s\n success!\n",LOGFILE_PATH);
  ret = read_filelist(LOGFILE_PATH);
  if(0 != ret){
    printf("read_filelist err!,ret =%d\n",ret);
    //return ret;
  }
  //文件名前缀
  if(strlen(prifix) > 0){
    strcpy(file_prifix,prifix);
  }
  //处理是否保留历史记录文件
  file_alives_proc();
  //for(int i = 0; i<30;i++){
  //  printf("%s\n",file_names[i]);
  //}
  //创建消息队列
  ret = open_msg();
  if(0 != ret){
    return ret;
  }
  printf("create msg quene success!\n");
  //创建写日志文件线程
  ret = pthread_create(&tid,
      NULL,
      thread_writelog,
      NULL);
  if(0 != ret)
  {
    fprintf(stderr, "couldn't create thread_writelog, errno %d\n", ret);
  }
  else
  {
    printf("create thread_writelog success!\n");
  }
  printf("init log success!enabled log file...\n");
#else
  printf("init log success!no enable log file...\n");
#endif
  return ret;
}
//
//


/*
//使用demo:
//mylog(DEBUG,"hello world!\n");
//输出:[2019-07-26 14:31:51:882] [DEBUG] comLib.c:1257] hello world!
//
//目前只为个人使用,暂无考虑线程安全,高效率和高并发
//考虑了一点儿效率,写文件操作IO比较耗时,因此日志使用了异步写入,linux消息队列。
//因linux的消息队列,容量和长度有限制,因此若单个消息超1024byte或并发发送几千个消息
//且发送速度很快,大于了队列的接收速度,那么肯定,会发送失败
*/
#ifndef LOG_H_
#define LOG_H_
#ifdef __cplusplus 
extern "C" {
#endif
enum LogLevel
{
    ERROR = 1,
    WARN  = 2,
    INFO  = 3,
    DEBUG = 4,
};
void mylog1(const char* filename, int line, enum LogLevel level, const char* fmt, ...) __attribute__((format(printf,4,5)));
#define mylog(level, format, ...) mylog1(__FILE__, __LINE__, level, format, ## __VA_ARGS__)
//================================================================
//============日志模块操作接口,配置和使用都在下面================
//日志级别定义,小于该级别的日志方能输出
#ifndef LOGLEVEL
#define LOGLEVEL DEBUG
#endif
//目前暂只支持管理目录下的30个日志文件名,文件名长度50以内。大了不处理
#define LOGFILE_MAXCOUNT  30
#define LOGFILE_NAMELENTH 50
//===========日志文件的配置=======================================
//是否启用记录日志到文件功能
#define LOGFILE_ENABLE 1
//日志文件的路径,后面不能带"/"
#define LOGFILE_PATH "/log"
//日志文件名称的前缀
#define LOGFILE_PREFIX "log_b503_"
//日志文件存在的时间 单位(天),会自动删除当前日期-ALIVEDAYS 之前的文件
//限制日志最长保留时间不能超 LOGFILE_MAXCOUNT 天
#define LOGFILE_ALIVEDAYS 7  
//单个日志文件的限制大小 单位kb
#define LOGFILE_MAXSIZE 1*1024 
//================================================================
//日志模块初始化,若要记录日志到文件中,必须init_log且开启LOGFILE_ENABLE
//若不需要记录日志到文件功能,则不要调用init_log();
//调用了也没事,只是别开启LOGFILE_ENABLE
//参数prifix,日志文件名前缀,最好传终端唯一编号
extern int init_log(char *prifix); 
#ifdef __cplusplus 
};
#endif
#


附:


go实现的简易FTP功能,先对日志文件进行zip压缩,然后ftp至后台服务器。


package main
import (
  "archive/zip"
  "flag"
  "fmt"
  "io"
  "log"
  "os"
  "strings"
  "github.com/dutchcoders/goftp"
  "github.com/larspensjo/config"
)
var (
  ftp     *goftp.FTP
  conFile        = flag.String("ftpcfg", "/ftpcfg.ini", "config file")
  Server  string = "127.0.0.1:21"
  User    string = ""
  Pwd     string = ""
)
func checkErr(err error) {
  if err != nil {
    fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
  }
}
/**
@files:需要压缩的文件
@compreFile:压缩之后的文件
*/
func CompressZip(files []*os.File, zipfileName string) (err error) {
  zipfile, err := os.Create(zipfileName)
  if err != nil {
    return err
  }
  defer zipfile.Close()
  zw := zip.NewWriter(zipfile)
  defer zw.Close()
  for _, file := range files {
    err := compressZip(file, zw)
    if err != nil {
      return err
    }
    file.Close()
  }
  return nil
}
/**
功能:压缩文件
@file:压缩文件
@prefix:压缩文件内部的路径
@tw:写入压缩文件的流
*/
func compressZip(file *os.File, zw *zip.Writer) error {
  info, err := file.Stat()
  if err != nil {
    log.Println("压缩文件失败:", err.Error())
    return err
  }
  // 获取压缩头信息
  head, err := zip.FileInfoHeader(info)
  if err != nil {
    log.Println("压缩文件失败:", err.Error())
    return err
  }
  // 指定文件压缩方式 默认为 Store 方式 该方式不压缩文件 只是转换为zip保存
  head.Method = zip.Deflate
  fw, err := zw.CreateHeader(head)
  if err != nil {
    log.Println("压缩文件失败:", err.Error())
    return err
  }
  // 写入文件到压缩包中
  _, err = io.Copy(fw, file)
  file.Close()
  if err != nil {
    log.Println("压缩文件失败:", err.Error())
    return err
  }
  return nil
}
func main() {
  fmt.Println("Hello Go")
  defer func() {
    if r := recover(); r != nil {
      fmt.Printf("crash occurred!capture:%s\n", r)
    }
  }()
  // fmt.Println(os.Args) //打印切片内容
  // for i := 0; i < len(os.Args); i++ {
  //  fmt.Println(os.Args[i])
  // }
  var fname string
  flag.StringVar(&fname, "fname", "", "file name")
  flag.Parse()
  flag.Usage()
  //获取当前路径
  file, _ := os.Getwd()
  cfg, err := config.ReadDefault(file + *conFile)
  checkErr(err)
  //获取配置文件中的配置项
  Server, err = cfg.String("SERVERCONFIG", "Server")
  User, err = cfg.String("USER", "User")
  Pwd, err = cfg.String("USER", "Pwd")
  //压缩ZIP
  var filezips []*os.File
  var filezip *os.File
  if filezip, err = os.Open(fname); err != nil {
    panic(err)
  }
  filezips = append(filezips, filezip)
  fmt.Println("CompressZip:" + fname + ".zip")
  err = CompressZip(filezips, fname+".zip")
  if err != nil {
    panic(err)
  }
  fmt.Println("User:" + User)
  fmt.Println("->begin connect server:" + Server)
  // For debug messages: goftp.ConnectDbg("ftp.server.com:21")
  if ftp, err = goftp.ConnectDbg(Server); err != nil {
    panic(err)
  }
  //ftp.debug = true
  defer ftp.Close()
  fmt.Println("->Successfully connected !!")
  // Username / password authentication
  if err = ftp.Login(User, Pwd); err != nil {
    panic(err)
  }
  fmt.Println("->Login success!")
  if err = ftp.Cwd("/"); err != nil {
    panic(err)
  }
  var curpath string
  if curpath, err = ftp.Pwd(); err != nil {
    panic(err)
  }
  fmt.Printf("Current path: %s\n", curpath)
  // Get directory listing
  // var files []string
  // if files, err = ftp.List(""); err != nil {
  //  panic(err)
  // }
  // fmt.Println("Directory listing:/n", files)
  // Upload a file
  var fileup *os.File
  if fileup, err = os.Open(fname + ".zip"); err != nil {
    panic(err)
  }
  fmt.Println("->begin upload file...")
  fpath := fname + ".zip"
  lastidx := strings.LastIndex(fname+".zip", "/")
  if lastidx != -1 {
    fpath = fname[lastidx:] + ".zip"
  }
  fmt.Printf("fpath:%s\n", fpath)
  if err := ftp.Stor(fpath, fileup); err != nil {
    panic(err)
  }
  fmt.Println("->upload file over!")
  err = os.Remove(fname + ".zip") //上传成功,删除zip文件
  if err != nil {
    fmt.Println("file remove Error!")
    panic(err)
  }
}


编译与使用:


GOOS=linux GOARCH=arm GOARM=7 go build ftp.go
ftp -fname=/log/log_b503_20190730.log
root@b_lcd:/app/opt
./ftp -fname=/log/log_b503_20190730.log
Hello Go
Usage of ./ftp:
  -fname string
        file name
  -ftpcfg string
        config file (default "/ftpcfg.ini")
User:qq8864
->begin connect server:015.3vftp.com:21
2019/07/30 17:44:08 begin receiveLine...
2019/07/30 17:44:09 < 220 Serv-U FTP Server v6.4 for WinSock ready...
2019/07/30 17:44:09 receiveLine over!
2019/07/30 17:44:09 220 Serv-U FTP Server v6.4 for WinSock ready...
->Successfully connected !!
2019/07/30 17:44:09 > USER qq8864
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:09 < 331 User name okay, need password.
2019/07/30 17:44:09 receiveLine over!
2019/07/30 17:44:09 > PASS sars1212
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:09 < 230 User logged in, proceed.
2019/07/30 17:44:09 receiveLine over!
->Login success!
2019/07/30 17:44:09 > CWD /
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:09 < 250 Directory changed to /
2019/07/30 17:44:09 receiveLine over!
2019/07/30 17:44:09 > PWD
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:09 < 257 "/" is current directory.
2019/07/30 17:44:09 receiveLine over!
Current path: /
->begin upload file...
fpath:/log_b503_20190730.log
2019/07/30 17:44:09 > TYPE I
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:11 < 200 Type set to I.
2019/07/30 17:44:11 receiveLine over!
2019/07/30 17:44:11 > PASV
2019/07/30 17:44:11 begin receiveLine...
2019/07/30 17:44:11 < 227 Entering Passive Mode (52,128,243,181,6,1)
2019/07/30 17:44:11 receiveLine over!
2019/07/30 17:44:11 > STOR /log_b503_20190730.log
2019/07/30 17:44:11 Connecting to 015.3vftp.com:1537
2019/07/30 17:44:11 begin receiveLine...
2019/07/30 17:44:11 < 150 Opening BINARY mode data connection for log_b503_20190730.log.
2019/07/30 17:44:11 receiveLine over!
2019/07/30 17:44:11 a1:150 Opening BINARY mode data connection for log_b503_20190730.log.
2019/07/30 17:44:11 begin receiveLine...
2019/07/30 17:44:13 < 226-Maximum disk quota limited to 102400 kBytes
2019/07/30 17:44:13 receiveLine over!
2019/07/30 17:44:13 begin receiveLine...
2019/07/30 17:44:13 <     Used disk quota 64 kBytes, available 102335 kBytes
2019/07/30 17:44:13 receiveLine over!
2019/07/30 17:44:13 begin receiveLine...
2019/07/30 17:44:13 < 226 Transfer complete.
2019/07/30 17:44:13 receiveLine over!
2019/07/30 17:44:13 a2:226-Maximum disk quota limited to 102400 kBytes
    Used disk quota 64 kBytes, available 102335 kBytes
226 Transfer complete.
->upload file over!
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
9天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
108 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
1月前
|
Rust 前端开发 JavaScript
Tauri 开发实践 — Tauri 日志记录功能开发
本文介绍了如何为 Tauri 应用配置日志记录。Tauri 是一个利用 Web 技术构建桌面应用的框架。文章详细说明了如何在 Rust 和 JavaScript 代码中设置和集成日志记录,并控制日志输出。通过添加 `log` crate 和 Tauri 日志插件,可以轻松实现多平台日志记录,包括控制台输出、Webview 控制台和日志文件。文章还展示了如何调整日志级别以优化输出内容。配置完成后,日志记录功能将显著提升开发体验和程序稳定性。
65 1
Tauri 开发实践 — Tauri 日志记录功能开发
|
1月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
209 3
|
1月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1619 14
|
1月前
|
Python
log日志学习
【10月更文挑战第9天】 python处理log打印模块log的使用和介绍
30 0
|
1月前
|
数据可视化
Tensorboard可视化学习笔记(一):如何可视化通过网页查看log日志
关于如何使用TensorBoard进行数据可视化的教程,包括TensorBoard的安装、配置环境变量、将数据写入TensorBoard、启动TensorBoard以及如何通过网页查看日志文件。
181 0
|
1月前
|
存储 分布式计算 NoSQL
大数据-136 - ClickHouse 集群 表引擎详解1 - 日志、Log、Memory、Merge
大数据-136 - ClickHouse 集群 表引擎详解1 - 日志、Log、Memory、Merge
39 0
|
1月前
|
缓存 Linux 编译器
【C++】CentOS环境搭建-安装log4cplus日志组件包及报错解决方案
通过上述步骤,您应该能够在CentOS环境中成功安装并使用log4cplus日志组件。面对任何安装或使用过程中出现的问题,仔细检查错误信息,对照提供的解决方案进行调整,通常都能找到合适的解决之道。log4cplus的强大功能将为您的项目提供灵活、高效的日志管理方案,助力软件开发与维护。
53 0
|
2月前
|
Java
日志框架log4j打印异常堆栈信息携带traceId,方便接口异常排查
日常项目运行日志,异常栈打印是不带traceId,导致排查问题查找异常栈很麻烦。
|
2月前
|
存储 监控 数据可视化
SLS 虽然不是直接使用 OSS 作为底层存储,但它凭借自身独特的存储架构和功能,为用户提供了一种专业、高效的日志服务解决方案。
【9月更文挑战第2天】SLS 虽然不是直接使用 OSS 作为底层存储,但它凭借自身独特的存储架构和功能,为用户提供了一种专业、高效的日志服务解决方案。
148 9