No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

log.service.ts 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import { Injectable, OnDestroy } from '@angular/core';
  2. import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
  3. import { LogEntry } from '../models/log.model';
  4. import { AuthService } from './auth.service';
  5. @Injectable({
  6. providedIn: 'root'
  7. })
  8. export class LogService implements OnDestroy {
  9. private logStreamSubject = new Subject<LogEntry>();
  10. logStream$ = this.logStreamSubject.asObservable();
  11. private logs: LogEntry[] = [];
  12. private logsSubject = new BehaviorSubject<LogEntry[]>([]);
  13. logs$ = this.logsSubject.asObservable();
  14. private eventSource: EventSource | null = null;
  15. private isConnected = false;
  16. private subscriptions: Subscription = new Subscription();
  17. private currentSessionId?: string;
  18. constructor(private authService: AuthService) {
  19. this.initializeAuthSubscription();
  20. }
  21. // 初始化认证状态订阅
  22. private initializeAuthSubscription() {
  23. this.subscriptions.add(
  24. this.authService.authState$.subscribe(authState => {
  25. console.log('LogService: 认证状态变化', authState.isAuthenticated);
  26. if (authState.isAuthenticated) {
  27. // 用户已登录,连接日志流
  28. this.connectToLogStream();
  29. } else {
  30. // 用户登出,断开日志流
  31. this.disconnect();
  32. // 添加日志通知
  33. this.addLog({
  34. id: Date.now().toString(),
  35. timestamp: new Date(),
  36. level: 'info',
  37. message: '用户未登录,日志流未连接',
  38. source: 'log-service'
  39. });
  40. }
  41. })
  42. );
  43. }
  44. // 连接到日志流
  45. connectToLogStream(sessionId?: string) {
  46. if (this.eventSource) {
  47. this.eventSource.close();
  48. }
  49. // 检查是否已登录
  50. if (!this.authService.isAuthenticated()) {
  51. console.warn('未登录状态,不连接日志流');
  52. return;
  53. }
  54. // 获取认证token
  55. const token = this.authService.getToken();
  56. if (!token) {
  57. console.error('无法获取认证token');
  58. return;
  59. }
  60. // 构建带认证参数的URL
  61. let url = `/api/logs/stream?token=${encodeURIComponent(token)}`;
  62. if (sessionId) {
  63. url += `&sessionId=${sessionId}`;
  64. }
  65. console.log('连接日志流URL:', url);
  66. this.eventSource = new EventSource(url);
  67. this.eventSource.onopen = () => {
  68. console.log('日志流连接已建立');
  69. this.isConnected = true;
  70. this.addLog({
  71. id: Date.now().toString(),
  72. timestamp: new Date(),
  73. level: 'info',
  74. message: '已连接到日志流',
  75. source: 'log-service'
  76. });
  77. };
  78. this.eventSource.onmessage = (event) => {
  79. const logData = event.data;
  80. if (logData && logData !== ': heartbeat') {
  81. const logEntry: LogEntry = {
  82. id: Date.now().toString(),
  83. timestamp: new Date(),
  84. level: 'info', // 默认级别,实际可能从日志中解析
  85. message: logData,
  86. source: 'opencode'
  87. };
  88. // 尝试解析日志级别
  89. if (logData.toLowerCase().includes('error')) {
  90. logEntry.level = 'error';
  91. } else if (logData.toLowerCase().includes('warn')) {
  92. logEntry.level = 'warn';
  93. } else if (logData.toLowerCase().includes('debug')) {
  94. logEntry.level = 'debug';
  95. }
  96. // 尝试提取会话ID
  97. const sessionMatch = logData.match(/session[:\s-]*([a-zA-Z0-9_-]+)/i);
  98. if (sessionMatch) {
  99. logEntry.sessionID = sessionMatch[1];
  100. }
  101. this.addLog(logEntry);
  102. }
  103. };
  104. this.eventSource.onerror = (error) => {
  105. console.error('日志流连接错误:', error);
  106. this.isConnected = false;
  107. // 检查是否仍然登录
  108. if (this.authService.isAuthenticated()) {
  109. this.addLog({
  110. id: Date.now().toString(),
  111. timestamp: new Date(),
  112. level: 'error',
  113. message: '日志流连接断开,正在重连...',
  114. source: 'log-service'
  115. });
  116. // 5秒后重连(仅在登录状态下)
  117. setTimeout(() => {
  118. if (this.authService.isAuthenticated()) {
  119. this.connectToLogStream(sessionId);
  120. }
  121. }, 5000);
  122. } else {
  123. this.addLog({
  124. id: Date.now().toString(),
  125. timestamp: new Date(),
  126. level: 'warn',
  127. message: '日志流连接断开(用户未登录)',
  128. source: 'log-service'
  129. });
  130. }
  131. };
  132. }
  133. // 断开日志流连接
  134. disconnect() {
  135. if (this.eventSource) {
  136. this.eventSource.close();
  137. this.eventSource = null;
  138. this.isConnected = false;
  139. this.addLog({
  140. id: Date.now().toString(),
  141. timestamp: new Date(),
  142. level: 'info',
  143. message: '已断开日志流连接',
  144. source: 'log-service'
  145. });
  146. }
  147. }
  148. // 添加日志
  149. private addLog(log: LogEntry) {
  150. this.logs.unshift(log); // 新的在前面
  151. if (this.logs.length > 1000) { // 限制日志数量
  152. this.logs.pop();
  153. }
  154. this.logsSubject.next([...this.logs]);
  155. this.logStreamSubject.next(log);
  156. }
  157. // 手动添加日志
  158. log(level: 'info' | 'warn' | 'error' | 'debug', message: string, source?: string, sessionId?: string) {
  159. const logEntry: LogEntry = {
  160. id: Date.now().toString(),
  161. timestamp: new Date(),
  162. level,
  163. message,
  164. source: source || 'app',
  165. sessionID: sessionId
  166. };
  167. this.addLog(logEntry);
  168. }
  169. // 获取所有日志
  170. getLogs(): LogEntry[] {
  171. return [...this.logs];
  172. }
  173. // 根据条件过滤日志
  174. getFilteredLogs(filter: { sessionId?: string; level?: string; limit?: number }): LogEntry[] {
  175. let filtered = [...this.logs];
  176. if (filter.sessionId) {
  177. filtered = filtered.filter(log => log.sessionID === filter.sessionId);
  178. }
  179. if (filter.level && filter.level !== 'all') {
  180. filtered = filtered.filter(log => log.level === filter.level);
  181. }
  182. if (filter.limit) {
  183. filtered = filtered.slice(0, filter.limit);
  184. }
  185. return filtered;
  186. }
  187. // 清空日志
  188. clearLogs() {
  189. this.logs = [];
  190. this.logsSubject.next([]);
  191. }
  192. // 检查连接状态
  193. isStreamConnected(): boolean {
  194. return this.isConnected;
  195. }
  196. // 切换会话过滤
  197. switchSession(sessionId?: string) {
  198. if (this.authService.isAuthenticated()) {
  199. this.connectToLogStream(sessionId);
  200. } else {
  201. console.warn('未登录状态,无法切换会话过滤');
  202. }
  203. }
  204. ngOnDestroy() {
  205. this.subscriptions.unsubscribe();
  206. this.disconnect();
  207. }
  208. }