import json,re,time,pathlib

# Only allowlisted structural fields survive; raw logs never reach a file.
class Observer:
    def __init__(self, path):
        self.path=pathlib.Path(path);self.start=time.monotonic()
        self.counts={};self.events=[];self.sequences={'submitted':set(),'done':set()}
        self.lines=0;self.last_save=0
    def consume(self,line):
        self.lines+=1
        obj=re.search(r'<((?:infer-infer|preprocess-(?:queue|preprocess)|retrieve-[a-z-]+)-\d+)(?::(sink|src))?>',line)
        now=round(time.monotonic()-self.start,6)
        if obj:
            target=obj[1]+(':'+obj[2] if obj[2] else '')
            code=None
            if 'GST_EVENT' in line and re.search(r'\beos\b',line,re.I):
                if 'sending event' in line:code='eos_send_enter'
                elif 'sent event' in line:code='eos_send_return'
                else:code='eos_other'
            elif 'GST_SCHEDULING' in line:
                if 'calling chainfunction' in line:code='chain_enter'
                elif 'called chainfunction' in line:code='chain_return'
            elif 'nvinferserver' in line:
                for text,kind in [('batch-inference buf:','submitted'),('inference done on buf:','done')]:
                    m=re.search(re.escape(text)+r'(\d+)',line)
                    if m:
                        code=kind
                        if len(self.sequences[kind])<1000:self.sequences[kind].add(int(m[1]))
                if 'sync event' in line:code='sync_enter'
            if code:
                key=target+'/'+code
                item=self.counts.setdefault(key,{'n':0,'first_s':now,'last_s':now})
                item['n']+=1;item['last_s']=now
                if code.startswith('eos') and len(self.events)<100:
                    self.events.append({'target':target,'code':code,'s':now})
        if time.monotonic()-self.last_save>1:self.save()
    def save(self):
        value={'lines_consumed':self.lines,'counts':self.counts,'eos':self.events,
               'submitted_ids':sorted(self.sequences['submitted']), 'done_ids':sorted(self.sequences['done']),
               'submitted_not_done':sorted(self.sequences['submitted']-self.sequences['done'])}
        self.path.write_text(json.dumps(value,indent=2));self.last_save=time.monotonic()

if __name__=='__main__':
    import tempfile
    with tempfile.TemporaryDirectory(dir=pathlib.Path(__file__).parent) as d:
        o=Observer(pathlib.Path(d)/'check.json')
        o.consume('GST_EVENT gstpad.c:1:func:<infer-infer-0:sink> sending event 0xabc (eos)')
        o.consume('nvinferserver file:1:func:<infer-infer-0> batch-inference buf:7')
        o.consume('nvinferserver file:1:func:<infer-infer-0> inference done on buf:7 status:SUCCESS')
        o.consume('PRIVATE_VALUE rtsp://secret.invalid/path')
        o.save();v=o.path.read_text()
        assert 'PRIVATE_VALUE' not in v and 'secret.invalid' not in v and '0xabc' not in v
        assert len(o.events)==1 and o.sequences['submitted']==o.sequences['done']=={7}
    print('sanitizer tests PASS')
