Hi, 大家好,我是姜友华。
在没有了解到ProtoBuf之前,我在后台开发中输出日志一般有两种:一种是基于现有格式,如JSON, XML等;另一种是基于约定输出字符串行。第一种大家都知道,第二种是类似于数库备份时导出CSV文件一样。
内容概要
- 首先,创建接收的日志服务器端。
- 其次,在iOS里使用ProtoBuf。
- 最后,输出ProtoBuf到文件,再读文件到ProtoBuf。
日志服务器。
我们使用go来建立一个最简的服务器,用来接收上传的文件。你当然也可以用其它的方式搭建一个。
下面是Go的代码,使用下面代码前,你需要有Go官网的开发环境。
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", uploadFile)
err := http.ListenAndServe(":80", nil)
if err != nil {
log.Fatal(err)
}
}
func uploadFile(w http.ResponseWriter, r *http.Request) {
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
b, err := ioutil.ReadAll(file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = ioutil.WriteFile(handler.Filename, b, 0644)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Upload successful")
}
运行它会监听本地的80端口。
jingo1997@163.com logServer % go run main.go
使用Postman测试一下,可行。
在iOS添加ProtoBuf
在本例中,我们将使用Proto3。Swift里使用ProtoBuf,需要引用第三方库。我选择了这个库Swift ProtoBuf。
iOS里是如何使用ProtoBuf的呢?
使用ProtoBuf分三步:首先,按Proto3的方式定义一个信息体,并保存;然后,使用Protoc工具将该信息体转为Swift结构体;最后,Swift使用该结构体进行日志输出。
- 安装Protoc工具。
$ brew install swift-protobuf.
将库引入到工程。
我们是通过Xocde自带的包管理引入的。
Screen Shot 2021-08-16 at 18.19.37.png
- 添加日志信息体: LogFile.proto。
- syntax = "proto3"; 需要在第一行。
- 成员索引从1开始。
- 内部枚举索引从0开始。
syntax = "proto3";
message LogInfo {
int32 id = 1;
string content = 2;
enum Event {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
enum State {
SUCCESS = 0;
INFO = 1;
ALART = 2;
ERROR = 3;
}
Event event = 3;
State state = 4;
}
- 转换日志信息体。
进入到logFile.proto的根目录,运行下面命令,输出一个LogInfo.pb.swift的文件。
$ protoc --swift_out=. logFile.proto
- 使用ProtoBuf。
我们创建一个Logger结构体,用来调用用ProtoBuf。
先贴代码:
/// Logger
struct Logger {
static var shared = Logger()
private var todayUrl: URL? {
let dateFormatter: DateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd"
let path = "\(dateFormatter.string(from: Date())).log"
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent(path)
}
func out(id: Int, content: String, event: LogInfo.Event, state: LogInfo.State){
var log = LogInfo()
log.id = Int32(id)
log.content = content
log.event = event
log.state = state
do {
let binaryData = try log.serializedData()
try binaryData.appendLine(to: todayUrl)
} catch {
print("Logger.out: \(error)")
return
}
}
func `in`() -> [LogInfo] {
var logs = [LogInfo]()
guard let todayUrl = todayUrl else {
print("Logger.read: Log url is error.")
return logs
}
if freopen(todayUrl.path, "r", stdin) == nil {
return logs
}
while let line = readLine() {
guard let log = try? LogInfo(serializedData: line.data(using: .utf8)!) else {
print("Line: ", line)
break
}
logs.append(log)
}
print("Log: ", logs.first)
return logs
}
func test(isIn: Bool = false){
// 从日志里读取。
if isIn {
self.in()
return
}
// 写到日志。
let contents = ["星期一Monday简写为Mon","星期二Tuesday简写为Tue","星期三Wednesday简写为Wed","星期四Thursday简写为Thu","星期五Friday简写为Fri","星期六Saturday简写为Sat","星期日Sunday简写为Sun"]
for (i, item) in contents.enumerated() {
self.out(id: i, content: item, event: .web, state: .info)
}
}
}
// 写。
extension Data {
func appendLine(to url: URL?) throws {
guard let url = url else {
return
}
print("Path:", url.path)
if let fileHandle = try? FileHandle(forWritingTo: url) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write("\n".data(using: .utf8)!)
fileHandle.write(self)
} else {
try write(to: url)
}
}
}
在这里我们做了两件事:
- 通过ProtoBuf,将数据写到日志文件上。
- 从日志文件读数据,转为ProtoBuf对象。
为了做这两件事,我们定义了一个Logger的结构体,同时拓展了Data类为其添加了一个appendLine的方法。
先来看结构体Logger:
- shard,静态方法,返回一个Logger实例。
- todayUrl,返回一个以当前日期命名时存储路程径。
- out,创一个LogInfo实例,并把它转为二进制数据,通过appendLine(),写在日志文件里。
- in,从当天的日志文件里读取内容并转为LogInfo实例。
- test, 提供了in、out方法的测试入口。
再来看Data类的appendLine方法:
*逐行写入每个LogInfo实例的二进制数据。
注意,各个LogInfo实例二进制数据之间的分割是通过换行("\n")实现的。
本地测试一下:
- 输出 LogInfo。
我们在AppDelegate中调用Logger的test方法。
/// AppDelegate
......
Logger.shared.test()
......
打开path/to/20210817.log文件:
是不是跟CSV文件相似,但还是有一点不同,我们的event, state的数据呢?
�星期一Monday简写为Mon�� �
�星期二Tuesday简写为Tue�� �
�星期三Wednesday简写为Wed�� �
�星期四Thursday简写为Thu�� �
�星期五Friday简写为Fri�� �
�星期六Saturday简写为Sat�� �
�星期日Sunday简写为Sun�� �� �
- 转入 LogInfo。
我们在AppDelegate中调用Logger的test方法。
- 逐行从日志文件里读取数据并转为LogInfo。
/// AppDelegate
......
Logger.shared.test(isIn: true)
......
- 控制台输出第一个对象:
Log: Optional(study.LogInfo:
content: "星期一Monday简写为Mon"
event: WEB
state: INFO
)
输出结果与测试输入的数据相同,应用该可以了。
等一下,其实缺少了id的数据。
- 我们改为输出最后一个
Log: Optional(study.LogInfo:
id: 6
content: "星期日Sunday简写为Sun"
event: WEB
state: INFO
)
这个有id值,这就对了。
好,这一节就到这里,下一节添加服务器端ProtoBuf解析。
我是姜友华,下次再见。
网友评论